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 wrote: > On 10 September 2014 23:26, Yotam Barnoy wrote: > > On Wed, Sep 10, 2014 at 6:20 PM, Jeremy Yallop wrote: > >> > >> On 10 September 2014 21:32, Yotam Barnoy 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. >