Thank you for the detailed explanation. That makes sense, as much as I wish there was a static detection mechanism in place for this kind of thing.

On Wed, Sep 10, 2014 at 6:50 PM, Jeremy Yallop <yallop@gmail.com> wrote:
On 10 September 2014 23:26, Yotam Barnoy <yotambarnoy@gmail.com> wrote:
> On Wed, Sep 10, 2014 at 6:20 PM, Jeremy Yallop <yallop@gmail.com> wrote:
>>
>> On 10 September 2014 21:32, Yotam Barnoy <yotambarnoy@gmail.com> wrote:
>> > I just encountered this nasty RUNTIME error in my code. What does it
>> > mean?
>> > How does it happen?
>>
>> The behaviour is documented in the manual:
>>
>>    http://caml.inria.fr/pub/docs/manual-ocaml-400/manual021.html#toc75
>>
>> See the paragraph beginning "Currently, the compiler requires [...]".
>
> Thanks. Does it make sense that changing a function definition from
> point-free to an explicit definition should eliminate this exception?

Yes, that's the expected behaviour.  Initially the fields in a
recursive module are bound to functions that raise an exception.  The
module initialization then overwrites each field with its actual
value, which is determined by evaluating the expression on the right
hand side of the field definition.  If evaluating the right hand side
involves reading one of the fields of the module that has not yet been
overwritten then the field will resolve to the exception-raising
function, which may lead to the runtime error that you've seen.

Here's an example.  Suppose you have a recursive module like this:

  module rec M1
    : sig val x : unit -> unit end =
  struct
      let x = M1.x
  end

Then the initial state of the module at runtime has x bound to a
function that raises an exception:

   module rec M1
     : sig val x : unit -> unit end =
   struct
       let x = fun _ -> raise Undefined_recursive_module
   end

Module initialization then overwrites the field with the result of
evaluating the right-hand side -- that is, by the result of evaluating
M1.x:

   M1.x <- (fun _ -> raise Undefined_recursive_module)

Calling M1.x will lead to the exception being raised:

   M1.x ()
     => raise Undefined_recursive_module

Now consider what happens when you eta-expand the definition of x.
Here's the source program

   module rec M2 : sig val x : unit -> unit end =
   struct
       let x = fun e -> M2.x e
   end

The initial state of the module at runtime is the same as for M1:

   module rec M2 : sig val x : unit -> unit end =
   struct
       let x = fun _ -> raise Undefined_recursive_module end
   end

Once again, module initialization overwrites the field with the result
of evaluating the right-hand side.  This time, however, evaluating the
right-hand side doesn't require resolving M2.x, since M2.x is under a
'fun' binding:

   M2.x <- (fun e -> M2.x e)

When you come to call M2.x the recursive reference resolves to the new
value of the field and evaluation proceeds as expected:

   M2.x ()
     => M2.x ()

As the manual says, all of this is subject to change, and it's best
not to rely on the current behaviour.  I recommend that you avoid
using recursive modules for value-level recursion, if possible.