1

I would like to make this functions recursive but I don't know where to start.

let rec rlist r n =
    if n < 1 then []
    else Random.int r :: rlist r (n-1);;

let rec divide = function
    h1::h2::t -> let t1,t2 = divide t in
        h1::t1, h2::t2
    | l -> l,[];;

let rec merge ord (l1,l2) = match l1,l2 with
    [],l | l,[] -> l
    | h1::t1,h2::t2 -> if ord h1 h2
        then h1::merge ord (t1,l2)
        else h2::merge ord (l1,t2);;

Is there any way to test if a function is recursive or not?

glennsl
  • 28,186
  • 12
  • 57
  • 75
Chen Yo
  • 105
  • 1
  • 2
  • 10
  • 5
    A lot of your questions look like Université Paris-Sud L3-Info courses, you should ask your teacher about your problems, they're here for that. And none of the 10 questions you asked have an accepted answer which is rude because people take a bit of their time for you. – Lhooq Nov 03 '16 at 11:47
  • 2
    What I find strange, too, is that you asked a question about the merge sort one year ago, http://stackoverflow.com/questions/33566414/ocaml-mergesort-and-time and the divide function was tail-recursive, so you definitely didn't do it last year because this is the first TP of this course https://www.lri.fr/~conchon/PFA/ and the TA use to give the "elegant" solution. I have doubts, then, about the "I created a function (mergesort) in ocaml but when I use it, the list is inverted." and I don't like it. This looks like "I didn't listen to the teacher, please help me". – Lhooq Nov 03 '16 at 11:51
  • 1
    also refer to http://stackoverflow.com/questions/23186717/verify-that-an-ocaml-function-is-tail-recursive – Pierre G. Nov 03 '16 at 12:40

2 Answers2

7

If you give a man a fish, you feed him for a day. But if you give him a fishing rod, you feed him for a lifetime.

Thus, instead of giving you the solution, I would better teach you how to solve it yourself.

A tail-recursive function is a recursive function, where all recursive calls are in a tail position. A call position is called a tail position if it is the last call in a function, i.e., if the result of a called function will become a result of a caller.

Let's take the following simple function as our working example:

let rec sum n = if n = 0 then 0 else n + sum (n-1)

It is not a tail-recursive function as the call sum (n-1) is not in a tail position because its result is then incremented by one. It is not always easy to translate a general recursive function into a tail-recursive form. Sometimes, there is a tradeoff between efficiency, readability, and tail-recursion.

The general techniques are:

  1. use accumulator
  2. use continuation-passing style

Using accumulator

Sometimes a function really needs to store the intermediate results, because the result of recursion must be combined in a non-trivial way. A recursive function gives us a free container to store arbitrary data - the call stack. A place, where the language runtime, stores parameters for the currently called functions. Unfortunately, the stack container is bounded, and its size is unpredictable. So, sometimes, it is better to switch from the stack to the heap. The latter is slightly slower (because it introduces more work to the garbage collector), but is bigger and more controllable. In our case, we need only one word to store the running sum, so we have a clear win. We are using less space, and we're not introducing any memory garbage:

let sum n = 
  let rec loop n acc = if n = 0 then acc else loop (n-1) (acc+n) in
  loop n 0

However, as you may see, this came with a tradeoff - the implementation became slightly bigger and less understandable.

We used here a general pattern. Since we need to introduce an accumulator, we need an extra parameter. Since we don't want or can't change the interface of our function, we introduce a new helper function, that is recursive and will carry the extra parameter. The trick here is that we apply the summation before we do the recursive call, not after.

Using continuation-passing style

It is not always the case when you can rewrite your recursive algorithm using an accumulator. In this case, a more general technique can be used - the continuation-passing style. Basically, it is close to the previous technique, but we will use a continuation in the place of an accumulator. A continuation is a function, that will actually postpone the work, that is needed to be done after the recursion, to a later time. Conventionally, we call this function return or simply k (for the continuation). Mentally, the continuation is a way of throwing the result of computation back into the future. "Back" is because you returning the result back to the caller, in the future, because, the result will be used not now, but once everything is ready. But let's look at the implementation:

let sum n = 
  let rec loop n k = if n = 0 then k 0 else loop (n-1) (fun x -> k (x+n)) in
  loop n (fun x -> x)

You may see, that we employed the same strategy, except that instead of int accumulator we used a function k as a second parameter. If the base case, if n is zero, we will return 0, (you can read k 0 as return 0). In the general case, we recurse in a tail position, with a regular decrement of the inductive variable n, however, we pack the work, that should be done with the result of the recursive function into a function: fun x -> k (x+n). Basically, this function says, once x - the result of recursion call is ready, add it to the number n and return. (Again, if we will use name return instead of k it could be more readable: fun x -> return (x+n)).

There is no magic here, we still have the same tradeoff, as with accumulator, as we create a new closure (functional object) at every recursive call. And each newly created closure contains a reference to the previous one (that was passed via the parameter). For example, fun x -> k (x+n) is a function, that captures two free variables, the value n and function k, that was the previous continuation. Basically, these continuations form a linked list, where each node bears a computation and all arguments except one. So, the computation is delayed until the last one is known.

Of course, for our simple example, there is no need to use CPS, since it will create unnecessary garbage and be much slower. This is only for demonstration. However, for more complex algorithms, in particular for those that combine results of two or more recursive calls in a non-trivial case, e.g., folding over a graph data structure.

So now, armed with the new knowledge, I hope that you will be able to solve your problems as easy as pie.

Testing for the tail recursion

The tail call is a pretty well-defined syntactic notion, so it should be pretty obvious whether the call is in a tail position or not. However, there are still few methods that allow one to check whether the call is in a tail position. In fact, there are other cases, when tail-call optimization may come into play. For example, a call that is right to the shortcircuit logical operator is also a tail call. So, it is not always obvious when a call is using the stack or it is a tail call. The new version of OCaml allows one to put an annotation at the call place, e.g.,

let rec sum n = if n = 0 then 0 else n + (sum [@tailcall]) (n-1)

If the call is not really a tail call, a warning is issued by a compiler:

Warning 51: expected tailcall

Another method is to compile with -annot option. The annotation file will contain an annotation for each call, for example, if we will put the above function into a file sum.ml and compile with ocamlc -annot sum.ml, then we can open sum.annot file and look for all calls:

"sum.ml" 1 0 41 "sum.ml" 1 0 64
call(
  stack
)

If we, however, put our third implementation, then the see that all calls are tail calls, e.g. grep call -A1 sum.annot:

call(
  tail
--
call(
  tail
--
call(
  tail
--
call(
  tail

Finally, you can just test your program with some big input, and see whether your program will fail with the stack overflow. You can even reduce the size of the stack, this can be controlled with the environment variable OCAMLRUNPARAM, for example, to limit the stack to one thousand words:

export OCAMLRUNPARAM='l=1000'
ocaml sum.ml
ivg
  • 34,431
  • 2
  • 35
  • 63
  • Excellent answer indeed. I just went through your explanation and cant seem to get the `ocaml -annot sum.ml` command to run. As this is an older question, is the test method still available? opam switch is `ocaml-base-compiler.4.12.0 ` and the response I get is ```~/G/b/src (main)> cat sum.ml let sum n = let rec loop n k = if n = 0 then k 0 else loop (n-1) (fun x -> k (x+n)) in loop n (fun x -> x) > ocaml -annot sum.ml /bin/ocaml: unknown option '-annot'.``` Thank you. – armand Jul 01 '21 at 12:43
  • @armand, yes, it was a typo, it should `ocamlc` (the compiler) not `ocaml` (the interpreter). Will update the answer. – ivg Jul 01 '21 at 15:01
  • @Lhooq you are correct, my friend. I apologize for the inconvienence. I meant to ping the person who had responded to me. Thank you for pointing it out. – armand Jul 02 '21 at 12:08
  • @ivg Indeed, that works. Thank you. If you wanted to check the continuation-passing style example. Where would you put the @tailcall? Also, you will want to fix the last ocaml sum.ml as wel – armand Jul 02 '21 at 12:08
  • 1
    You should annotate all recursive calls. In that case all calls to `loop`. And the last example is correct, it should be `ocaml`, it calls the interpreter to run the code, not to compile. – ivg Jul 04 '21 at 13:38
  • thank you, sir. So it would look something like ``` let sum n = let rec loop n k = if n = 0 then k 0 else ((loop)[@tailcall ]) (n - 1) (fun x -> k (x + n)) in ((loop)[@tailcall ]) n (fun x -> x)``` – armand Jul 04 '21 at 13:58
3

You could do the following :

let rlist r n =
  let aux acc n =
    if n < 1 then acc
    else aux (Random.int r :: acc) (n-1)
  in aux [] n;;

let divide l =
  let aux acc1 acc2 = function
    | h1::h2::t -> 
        aux (h1::acc1) (h2::acc2) t
    | [e] -> e::acc1, acc2
    | [] -> acc1, acc2
  in aux [] [] l;;

But for divide I prefer this solution :

 let divide l =
   let aux acc1 acc2 = function
     | [] -> acc1, acc2
     | hd::tl -> aux acc2 (hd :: acc1) tl
   in aux [] [] l;;

let merge ord (l1,l2) = 
  let rec aux acc l1 l2 = 
    match l1,l2 with
      | [],l | l,[] -> List.rev_append acc l
      | h1::t1,h2::t2 -> if ord h1 h2
        then aux (h1 :: acc) t1 l2
        else aux (h2 :: acc) l1 t2
  in aux [] l1 l2;;

As to your question about testing if a function is tail recursive or not, by looking out for it a bit you would have find it here.

Community
  • 1
  • 1
Lhooq
  • 4,281
  • 1
  • 18
  • 37