I have a function called zip_with_2_fs. I am trying to make it tail recursive (zip_with_2_fs_tr) but I don't really understand what I am doing. I am new to Ocaml and would like to develop a deeper understanding of tail recursion and helper functions.
let rec zip_with_2_fs fx fy xs ys =
match (xs, ys) with
| ([], []) -> []
| ([], _) -> []
| (_, []) -> []
| (xh::xt, yh::yt) -> (fx xh, fy yh)::zip_with_2_fs fx fy xt yt;;
How would I go about fixing this?
let zip_with_2_fs_tr fx fy xs ys =
let rec helper fxx fy xss yss =
match (xs, ys) with
| ([], []) -> []
| ([], _) -> []
| (_, []) -> []
| (xh::xt, yh::yt) -> (fx xh, fy yh)::helper fx fy xt yt
in helper fx fy xs ys;;
The expression, (fx xh, fy yh)::helper fx fy xt yt is not tail recursive, because in order to reduce it to a value you need the result of the recursive call. E.g., let's rewrite it in a more verbose way, just to give names to things (it is easier to speak about things when they have names),
let head = (fx xh, fy yh) in
let tail = helper fx fy xt yt in (* recursive call *)
let result = head :: tail in
result
So here, we are making a recursive call and the result of the current call is not determined until this call returns, therefore our calls accumulate (as the recursive call will make its own call and wait until the result is ready, and so on) and we have precious stack space being spent to store all those intermediate results.
So the trick is that a value returned by the recursive call shall be the result of the calling function. The concept of a tail call is actually more general that the tail-recursive call. As any call whose result becomes the result of a caller is called a tail call and could be made without expending the stack space, e.g., speaking C,
int bar(int x) {return x + 1; }
int foo(int x) {return bar(x+1);}
Here we have a tail-call from foo to bar and the compiler may easily implement it with just a jump to bar without having to allocate a new stack frame or taking care of passing the value retuned from bar.
Back to OCaml. So we need to make sure that the value returned by our recursive function is the value returned by the recursive call. But there is no free lunch here, since we need to have some space where we will accumulate our intermediate result. In the non-tail recursive function version it was the program stack. Instead of the program stack, we can explicitly create a value and accumulate values in it during our recursive calls, e.g.,
let zip_with_f_fs fx fy xs ys =
let rec loop acc xs ys = match xs, ys with
| [],[] -> List.rev acc
| x::xs,y::ys -> loop ((fx x, fy y)::acc) xs ys
| _ -> failwith "uneven lists" in
loop [] xs ys
Now you can see that we have a recursive call to loop
| x::xs,y::ys -> loop ((fx x, fy y)::acc) xs ys
that is in the tail position, and we store our result in the first parameter called acc and once we reach the end of both lists, we reverse it (because we were pretending it, in fact acc was our stack) to get the values in the right order.
Related
I am really confused on how to make a function "Tail recursive".
Here is my function, but I don't know whether it is already tail recursive or not.
I am trying to merge two lists in Haskell.
merge2 :: Ord a =>[a]->[a]->[a]
merge2 xs [] = xs
merge2 [] ys = ys
merge2 (x:xs)(y:ys) = if y < x then y: merge2 (x:xs) ys else x :merge2 xs (y:ys)
Your function isn't tail-recursive; it's guarded recursive. However, guarded recursion is what you should be using in Haskell if you want to be memory efficient.
For a call to be a tail call, its result must be the result of the entire function. This definition applies to both recursive and non-recursive calls.
For example, in the code
f x y z = (x ++ y) ++ z
the call (x ++ y) ++ z is a tail call because its result is the result of the entire function. The call x ++ y is not a tail call.
For an example of tail-recursion, consider foldl:
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl _ acc [] = acc
foldl f acc (x:xs) = foldl f (f acc x) xs
The recursive call foldl f (f acc x) xs is a tail-recursive call because its result is the result of the entire function. Thus it's a tail call, and it is recursive being a call of foldl to itself.
The recursive calls in your code
merge2 (x:xs) (y:ys) = if y < x then y : merge2 (x:xs) ys
else x : merge2 xs (y:ys)
are not tail-recursive because they do not give the result of the entire function. The result of the call to merge2 is used as a part of the whole returned value, a new list. The (:) constructor, not the recursive call, gives the result of the entire function. And in fact, being lazy, (:) _ _ returns right away, and the holes _ are filled only later, if and when needed. That's why guarded recursion is space efficient.
However, tail-recursion doesn't guarantee space efficiency in a lazy language.
With lazy evaluation, Haskell builds up thunks, or structures in memory that represent code that is yet to be evaluated. Consider the evaluation of the following code:
foldl f 0 (1:2:3:[])
=> foldl f (f 0 1) (2:3:[])
=> foldl f (f (f 0 1) 2) (3:[])
=> foldl f (f (f (f 0 1) 2) 3) []
=> f (f (f 0 1) 2) 3
You can think of lazy evaluation as happening "outside-in." When the recursive calls to foldl are evaluated, thunks are built-up in the accumulator. So, tail recursion with accumulators is not space efficient in a lazy language because of the delayed evaluation (unless the accumulator is forced right away, before the next tail-recursive call is made, thus preventing the thunks build-up and instead presenting the already-calculated value, in the end).
Rather than tail recursion, you should try to use guarded recursion, where the recursive call is hidden inside a lazy data constructor. With lazy evaluation, expressions are evaluated until they are in weak head normal form (WHNF). An expression is in WHNF when it is either:
A lazy data constructor applied to arguments (e.g. Just (1 + 1))
A partially applied function (e.g. const 2)
A lambda expression (e.g. \x -> x)
Consider map:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
map (+1) (1:2:3:[])
=> (+1) 1 : map (+1) (2:3:[])
The expression (+1) 1 : map (+1) (2:3:[]) is in WHNF because of the (:) data constructor, and therefore evaluation stops at this point. Your merge2 function also uses guarded recursion, so it too is space-efficient in a lazy language.
TL;DR: In a lazy language, tail-recursion can still take up memory if it builds up thunks in the accumulator, while guarded recursion does not build up thunks.
Helpful links:
https://wiki.haskell.org/Tail_recursion
https://wiki.haskell.org/Stack_overflow
https://wiki.haskell.org/Thunk
https://wiki.haskell.org/Weak_head_normal_form
Does Haskell have tail-recursive optimization?
What is Weak Head Normal Form?
I have been working on a separate function that returns a list that inserts element x after each k elements of list l (counting from
the end of the list). For example, separate (1, 0, [1,2,3,4]) should return [1,0,2,0,3,0,4]. I finished the function and have it working as follows:
fun separate (k: int, x: 'a, l: 'a list) : 'a list =
let
fun kinsert [] _ = []
| kinsert ls 0 = x::(kinsert ls k)
| kinsert (l::ls) i = l::(kinsert ls (i-1))
in
List.rev (kinsert (List.rev l) k)
end
Im now trying to simplify the function using foldl/foldr without any recursion, but I cant seem to get it working right. Any tips/suggestions on how to approach this? Thank You!
These are more or less the thoughts I had when trying to write the function using foldl/foldr:
foldl/foldr abstracts away the list recursion from the logic that composes the end result.
Start by sketching out a function that has a much similar structure to your original program, but where foldr is used and kinsert instead of being a recursive function is the function given to foldr:
fun separate (k, x, L) =
let fun kinsert (y, ys) = ...
in foldr kinsert [] L
end
This isn't strictly necessary; kinsert might as well be anonymous.
You're using an inner helper function kinsert because you need a copy of k (i) that you gradually decrement and reset to k every time it reaches 0. So while the list that kinsert spits out is equivalent to the fold's accumulated variable, i is temporarily accumulated (and occasionally reset) in much the same way.
Change kinsert's accumulating variable to make room for i:
fun separate (k, x, L) =
let fun kinsert (y, (i, xs)) = ...
in foldr kinsert (?, []) L
end
Now the result of the fold becomes 'a * 'a list, which causes two problems: 1) We only really wanted to accumulate i temporarily, but it's part of the final result. This can be circumvented by discarding it using #2 (foldr ...). 2) If the result is now a tuple, I'm not sure what to put as the first i in place of ?.
Since kinsert is a separate function declaration, you can use pattern matching and multiple function bodies:
fun separate (k, x, L) =
let fun kinsert (y, (0, ys)) = ...
| kinsert (y, (i, ys)) = ...
in ... foldr kinsert ... L
end
Your original kinsert deviates from the recursion pattern that a fold performs in one way: In the middle pattern, when i matches 0, you're not chopping an element off ls, which a fold would otherwise force you to. So your 0 case will look slightly different from the original; you'll probably run into an off-by-one error.
Remember that foldr actually visits the last element in the list first, at which point i will have its initial value, where with the original kinsert, the initial value for i will be when you're at the first element.
Depending on whether you use foldl or foldr you'll run into different problems: foldl will reverse your list, but address items in the right order. foldr will keep the list order correct, but create a different result when k does not divide the length of L...
At this point, consider using foldl and reverse the list instead:
fun separate (k, x, L) =
let fun kinsert (y, (?, ys)) = ...
| kinsert (y, (i, ys)) = ...
in rev (... foldl kinsert ... L)
end
Otherwise you'll start to notice that separate (2, 0, [1,2,3,4,5]) should probably give [1,2,0,3,4,0,5] and not [1,0,2,3,0,5].
My Quicksort code works for some values of N (size of list), but for big values (for example, N = 82031) the error returned by OCaml is:
Fatal error: exception Stack_overflow.
What am I doing wrong?
Should I create an iterative version due to the fact that OCaml does not support recursive functions for big values?
let rec append l1 l2 =
match l1 with
| [] -> l2
| x::xs -> x::(append xs l2)
let rec partition p l =
match l with
| [] -> ([],[])
| x::xs ->
let (cs,bs) = partition p xs in
if p < x then
(cs,x::bs)
else
(x::cs,bs)
let rec quicksort l =
match l with
| [] -> []
| x::xs ->
let (ys, zs) = partition x xs in
append (quicksort ys) (x :: (quicksort zs));;
The problem is that none of your recursive functions are tail-recursive.
Tail-recursivity means that no further actions should be done by the caller (see here). In that case, there is no need to keep the environment of the caller function and the stack is not filled with environments of recursive calls. A language like OCaml can compile that in an optimal way but for this you need to provide tail-recursive functions.
For example, your first function, append :
let rec append l1 l2 =
match l1 with
| [] -> l2
| x::xs -> x::(append xs l2)
As you can see, after append xs l2 has been called, the caller needs to execute x :: ... and this function end up by not being tail-recursive.
Another way of doing it in a tail-recursive way is this :
let append l1 l2 =
let rec aux l1 l2 =
match l1 with
| [] -> l2
| x::xs -> append xs (x :: l2)
in aux (List.rev l1) l2
But, actually, you can try to use List.rev_append knowing that this function will append l1 and l2 but l1 will be reversed (List.rev_append [1;2;3] [4;5;6] gives [3;2;1;4;5;6])
Try to transform your other functions in tail-recursive ones and see what it gives you.
Best to fix the underlying problem as noted above, but if you really need a big stack, set ulimit -s. See also:
https://stackoverflow.com/a/71375559/14055985
fun p(L) =
[L] # p( tl(L) # [hd(L)] );
If L is [1,2,3] then I want to have a [ [1,2,3], [2,3,1], [3,1,2] ].
Since every time I append the first num to the end, then if L = [] then [] doesn't work here.
How to stop the function once it has the three lists?
You can have a parameter x in the function to keep track of how many levels deep in the recursion you are.
fun p(L, x) =
if x < length(L) then [L] # p(tl(L) # [hd(L)], x+1)
else [];
Then call the function with x=0.
p([1, 2, 3], 0)
And if you don't like the extra parameter, then as you probably know you can define another function and make it equal to the p function with the parameter forced to 0.
fun p0(L) = p(L, 0);
p0([1, 2, 3]); (* same result as p([1, 2, 3], 0); *)
Let me show some more implementation variants.
First of all, let's define an auxiliary function, which rotates a list 1 position to the left:
(* operates on non-empty lists only *)
fun rot1_left (h :: tl) = tl # [h]
Then the p function could be defined as follows:
fun p xs =
let
(* returns reversed result *)
fun loop [] _ _ = []
| loop xs n res =
if n = 0
then res
else loop (rot1_left xs) (n-1) (xs :: res)
in
List.rev (loop xs (length xs) [])
end
It's usually better (performance-wise) to add new elements at the beginning of the list and then reverse the resulting list once, than to append to the end many times. Note: this version does one spurious rotate at the end and I could have optimized it out, but didn't, to make code more clear.
We have calculated the length of the given list to make its rotated "copies", but we don't have to traverse xs beforehand, we can do it as we rotate it. So, we can use xs as a kind of counter, recursively calling the loop helper function on the tail of the xs list.
fun p xs =
let
(* returns reversed result *)
fun loop [] _ _ = []
| loop xs [] res = res
| loop xs (_::tl) res =
loop (rot1_left xs) tl (xs :: res)
in
List.rev (loop xs xs [])
end
Having done that, we are now closer to implementing p as a foldl function:
fun p xs =
(List.rev o #1)
(List.foldl
(fn (_, (res, rot)) => (rot::res, rot1_left rot))
([], xs)
xs)
The second argument to the List.foldl function is our "accumulator", which is represented here as a pair of the current (partial) result as in the previous implementations and the current rotated list. That explains (List.rev o #1) part: we need to take the first component of the accumulator and reverse it. And as for the ([], xs) part -- the current result is empty at the beginning (hence []) and we start rotating the initial xs list. Also, the _ in (_, (res, rot)) means the current element of the given xs, which we don't care about, since it just serves as a counter (see the prev. variant).
Note: o stands for function composition in Standard ML.
I'm writting a recursive function that use specific formulas to calculate 2 lists.
But I will simplify the function so you can understand the problem I'm having because the point here is to detect [] of the list.
So I've the following code:
listSum::([Integer],[Integer])->Double
listSum ((x:xs),(y:ys))
| ((x:xs),(y:ys))==(x:[],y:[])=0.0
| otherwise = (((fromIntegral x)::Double)+((fromIntegral y)::Double)) + listSum ((xs),(ys))
Output I'm having right now
listSum([1,2],[1,2])
2.0
listSum([1],[1])
0.0
listSum([],[])
*** Exception: file.hs: .....: Non-exhaustive patterns in function ListSum
And the output I wish to have
listSum([1,2],[1,2])
6.0
listSum([1],[1])
2.0
listSum([],[])
0.0
What did I miss? Or did I write too much?
You don't need the first guard in your function. You can simply write it as the following (I just dropped ::Double because Haskell can infer it)
listSum :: ([Integer], [Integer]) -> Double
listSum ([], []) = 0.0
listSum ((x:xs),(y:ys)) = fromIntegral x + fromIntegral y + listSum (xs, ys)
Now, whenever the arguments passed to listSum are empty lists, the result will be 0.0, otherwise the recursive function will be called.
Note: The above function will work only if both the lists are of equal size. Otherwise, you need to write it like this
listSum::([Integer],[Integer])->Double
listSum ([], []) = 0.0
listSum ((x:xs), []) = fromIntegral x + listSum(xs, [])
listSum ([], (y:ys)) = fromIntegral y + listSum(ys, [])
listSum ((x:xs),(y:ys)) = fromIntegral x + fromIntegral y + listSum (xs, ys)
Note: Even simpler, the entire code can be written, as suggested by Rein Henrichs, like this
pairwiseSum xs ys = sum (zipWith (+) xs ys)