I am learning functional programming using Haskell. I am trying to create a function that takes a function f, and executes the function on some input x, n number of times.
repeat :: (a -> a) -> a -> Int -> [a]
repeat f x n
So, the output list is like this:
[x, f(x), f(f(x)), ..., f^n(x)]
So far I have been able to come up with a function that I believe does this. It performs n times:
fn f a n = f a : fn f (f a) (n-1)
The problem I have now, is that I want the first element of the list to just be x. Right now, it is starting my list off at f(x). I tried playing around, but I'm not sure how to cover this "base" case in Haskell. Could someone lend a hand?
A proper recursive function must always have a base case: The base case is a way to return without making a recursive call. In other words, it is the mechanism that stops this process of ever more recursive calls and an ever growing stack of function calls waiting on the return of other function calls.
A recursive implementation may have more than one base case, or more than one recursive step. For example, the Fibonacci function has two base cases, n=0 and n=1.
A recursive function calls itself, the memory for a called function is allocated on top of memory allocated to calling function and different copy of local variables is created for each function call.
The base case occurs when n is zero, at which point recursion stops. In the recursive call, the argument is one less than the current value of n, so each recursion moves closer to the base case. Note: For simplicity, countdown () doesn’t check its argument for validity.
Recursion provides a clean and simple way to write code. Some problems are inherently recursive like tree traversals, Tower of Hanoi, etc. For such problems, it is preferred to write recursive code. We can write such codes also iteratively with the help of a stack data structure.
The process in which a function calls itself directly or indirectly is called recursion and the corresponding function is called as recursive function. Using recursive algorithm, certain problems can be solved quite easily. Examples of such problems are Towers of Hanoi (TOH), Inorder/Preorder/Postorder Tree Traversals, DFS of Graph, etc.
You apply the function in two places: once in the "simple" first element case and again in the recursion.
fn f a n = f a : fn f (f a) (n-1)
^^^ ^^^
If you want the first element to be a
, simply remove the first one; you don't need it.
fn f a n = a : fn f (f a) (n-1)
Now we have a working function, but it will run forever. You also need a base case. If we tell a function to repeat zero times, we should get the empty list.
fn _ _ 0 = []
fn f a n = a : fn f (f a) (n-1)
Finally, for readability and by usual Haskell coding standards, we'll want to give this thing a type signature. The most general possible type, which we can query from ghci
is
*Main> :t fn
fn :: (Eq t1, Num t1) => (t2 -> t2) -> t2 -> t1 -> [t2]
But it's highly unlikely we're ever going to call this thing with non-Int
values, and the extra typeclasses may just confuse the inference engine when trying to call it later, so I recommend
fn :: (a -> a) -> a -> Int -> [a]
fn _ _ 0 = []
fn f a n = a : fn f (f a) (n-1)
I would advise to implement this with two functions: one where we construct an infinite list, so (a -> a) -> a -> [a]
, and then we can construct a function Int -> [a] -> [a]
which takes the first n items. Both functions exist in Haskell's base
package. Indeed, the fn
function is implemented as iterate :: (a -> a) -> a -> [a]
and take :: Int -> [a] -> [a]
.
We can implement iterate
as:
iterate :: (a -> a) -> a -> [a]
iterate f = go
where go x = x : go (f x)
here x :
thus yields x
and makes a recursive call with go (f x)
so the recursive call will yield f(x)
, and then f(f(x))
, etc.
The take
function can be implemented as:
take :: Int -> [a] -> [a]
take n _ | n <= 0 = []
take _ [] = []
take n (x:xs) = x : take (n-1) xs
Your fn
function is then:
fn :: (a -> a) -> a -> Int -> [a]
fn f x n = take n (iterate f x)
Now we thus have two extra utility functions that can be used, not per se for our fn
function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With