Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursive modules - awkward dynamic semantics #2629

Closed
vicuna opened this issue May 31, 2004 · 5 comments
Closed

Recursive modules - awkward dynamic semantics #2629

vicuna opened this issue May 31, 2004 · 5 comments

Comments

@vicuna
Copy link

vicuna commented May 31, 2004

Original bug ID: 2629
Reporter: administrator
Status: acknowledged
Resolution: open
Priority: normal
Severity: feature
Category: typing
Tags: recmod
Monitored by: "Julien Signoles"

Bug description

Hi,

there is a very confusing issue concerning the dynamic semantics of
recursive modules. Please consider the following code:


module type S = sig
val f : unit -> unit
val g : unit -> unit
end

module MakeA (X : S) = struct
let g = X.f
let f () = print_endline "f"
end

module MakeB (X : S) = struct
include X
end

module rec A : S = MakeA (B)
and B : S = MakeB (A)

let () = B.g ()

The above code will fail with an exception "Undefined_recursive_module",
but IMHO it should print "f".

Eta-expanding the definition of "g" in "MakeA" solves the problem.

This seems a little bit awkward to me. I think that this should be
handled transparently, i.e. that intermediate bindings are also updated
when the recursive definition is completely evaluated rather than letting
them point to the dummy function that throws an exception.

What do you think, could this be fixed?

Best regards,
Markus

--
Markus Mottl http://www.oefai.at/~markus markus@oefai.at

@vicuna
Copy link
Author

vicuna commented Jun 20, 2004

Comment author: administrator

Dear Markus,

there is a very confusing issue concerning the dynamic semantics of
recursive modules. Please consider the following code:


module type S = sig
val f : unit -> unit
val g : unit -> unit
end

module MakeA (X : S) = struct
let g = X.f
let f () = print_endline "f"
end

module MakeB (X : S) = struct
include X
end

module rec A : S = MakeA (B)
and B : S = MakeB (A)

let () = B.g ()

The above code will fail with an exception "Undefined_recursive_module",
but IMHO it should print "f".
Eta-expanding the definition of "g" in "MakeA" solves the problem.

This is consistent with the operational interpretation of "module rec"
outlined in the reference manual: initialize A and B with default
functions that raise Undefined_recursive_module, compute the functor
applications, then update A and B. The eta-expansion is indeed
necessary to ensure that the reference to B.f in MakeA does not
evaluate to the default function.

This seems a little bit awkward to me. I think that this should be
handled transparently, i.e. that intermediate bindings are also updated
when the recursive definition is completely evaluated rather than letting
them point to the dummy function that throws an exception.

I see no way to do this while remaining in a call-by-value semantics.
What I plan to do at some point in the future is to implement a
compile-time analysis of "module rec" definitions that will signal an
error on examples such as yours.

Best regards,

  • Xavier Leroy

@vicuna
Copy link
Author

vicuna commented Jun 20, 2004

Comment author: administrator

Feature wish: compile-time detection of ill-founded "module rec"

@vicuna
Copy link
Author

vicuna commented Jun 20, 2004

Comment author: administrator

Dear Xavier,

On Sun, 20 Jun 2004, Xavier Leroy wrote:

This is consistent with the operational interpretation of "module rec"
outlined in the reference manual: initialize A and B with default
functions that raise Undefined_recursive_module, compute the functor
applications, then update A and B. The eta-expansion is indeed
necessary to ensure that the reference to B.f in MakeA does not
evaluate to the default function.

Well, it's certainly in accordance with the specification, but since
the dynamic semantics of recursive modules is still experimental, I
thought it might be interesting to consider alternatives to the current
implementation.

I see no way to do this while remaining in a call-by-value semantics.
What I plan to do at some point in the future is to implement a
compile-time analysis of "module rec" definitions that will signal an
error on examples such as yours.

It might be possible to use indirections for function values, but this
would probably require too many changes in the runtime and lead to heavy
performance penalties with function calls.

One workaround I had tried was to call a dummy-functor with the recursive
module. This functor just eta-expanded all definitions, and it's result
was included in the recursive module. If I remember correctly (?),
this actually seemed to work to some extent before bug #2639 (module
thinning) was fixed. Now I have to eta-expand all function definitions
by hand in each recursive module implementation which happens to have
the same interface. This can be quite tedious.

I actually wanted to use this trick ("include"ing recursive modules)
to get some open recursion, i.e. one can plug in additional module
layers that perform intermediate computations (e.g. logging execution of
functions, etc.). Well, it seems choosing objects + inheritance, which I
usually avoid when possible, would have been the better design decision...

Best regards,
Markus

--
Markus Mottl http://www.oefai.at/~markus markus@oefai.at

@github-actions
Copy link

This issue has been open one year with no activity. Consequently, it is being marked with the "stale" label. What this means is that the issue will be automatically closed in 30 days unless more comments are added or the "stale" label is removed. Comments that provide new information on the issue are especially welcome: is it still reproducible? did it appear in other contexts? how critical is it? etc.

@github-actions github-actions bot added the Stale label May 18, 2020
@xavierleroy
Copy link
Contributor

Maybe the semantics is "awkward", or maybe it's just let-rec in call-by-value that demands eta-expansion sometimes. At any rate the dynamic semantics and lack of static analysis have remained much the same in 16 years, so I'm closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants