8

I (believe) the following function definition is tail-recursive:

fun is_sorted [] = true
  | is_sorted [x] = true
  | is_sorted (x::(y::xs)) =
    if x > y
       then false
       else is_sorted (y::xs)

Trivially, it's equivalent to the following declaration

fun is_sorted [] = true
  | is_sorted [x] = true
  | is_sorted (x::(y::xs)) = 
    (x <= y) andalso (is_sorted (y::xs))

Yet in this version the last step is to apply the 'andalso', so its not tail-recursive. Or it would seem so, except that since (at least Standard) ML (of NJ) uses short-circuit evaluation, the andalso is in fact /not/ the last step. So would this function have tail-call optimization? Or there any other interesting instances where an ML function that does not obviously use tail-recursion in fact gets optimized?

Zach Halle
  • 321
  • 1
  • 9
  • I can tell you that the last time I read the assembly generated by OCaml, the call in `fun x -> let r = g x in r` was not compiled as a tail call. – Pascal Cuoq Jul 17 '14 at 13:42
  • I don't think so. Haskell replies quite heavily on compiler to do this kind of optimisation. But OCaml's philosophy is to leave this level of optimisation to developer and generally its compiler just compile direct on whatever the developer writes. – Jackson Tale Jul 17 '14 at 14:00
  • 1
    Seems to me your function gets the wrong answer for the list `[3, 4, 2, 3, 4]` – Jeffrey Scofield Jul 17 '14 at 15:54
  • Good point, I'll modify the definition so that the recursive call is "is_sorted (y::xs)" – Zach Halle Jul 18 '14 at 14:46
  • The compiler is fully aware of the short-circuiting nature of `andalso`, and probably transforms it to the equivalent conditional first. I would expect any half-decent compiler to handle the tail call properly, especially since it adds very little work for the compiler writer. – molbdnilo Aug 05 '14 at 10:22

2 Answers2

9

Note that

A andalso B

is equivalent to

if A then B else false

The SML language definition even defines it that way. Consequently, B is in tail position. No fancy optimisation necessary.

Andreas Rossberg
  • 34,518
  • 3
  • 61
  • 72
6

If I translate your second function to OCaml I get this:

let rec is_sorted : int list -> bool = function
| [] -> true
| [_] -> true
| x :: y :: xs -> x < y && is_sorted xs

This is compiled as a tail-recursive function by ocamlopt. The essence of the generated code (x86_64) is this:

        .globl      _camlAndalso__is_sorted_1008
_camlAndalso__is_sorted_1008:
        .cfi_startproc
.L103:
        cmpq        $1, %rax
        je  .L100
        movq        8(%rax), %rbx
        cmpq        $1, %rbx
        je  .L101
        movq        (%rbx), %rdi
        movq        (%rax), %rax
        cmpq        %rdi, %rax
        jge .L102
        movq        8(%rbx), %rax
        jmp .L103
        .align      2
.L102:
        movq        $1, %rax
        ret
        .align      2
.L101:
        movq        $3, %rax
        ret
        .align      2
.L100:
        movq        $3, %rax
        ret
        .cfi_endproc

As you can see there are no recursive calls in this code, just a loop.

(But other posters are right that OCaml doesn't do all that much in the way of sophisticated analysis. This particular result seems pretty straightforward.)

Jeffrey Scofield
  • 65,646
  • 2
  • 72
  • 108