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

Pushing the evaluation of default expression for optional arguments is not specified #5975

Closed
vicuna opened this issue Apr 4, 2013 · 7 comments
Assignees
Milestone

Comments

@vicuna
Copy link

vicuna commented Apr 4, 2013

Original bug ID: 5975
Reporter: @alainfrisch
Assigned to: @alainfrisch
Status: resolved (set by @alainfrisch on 2015-12-03T12:57:35Z)
Resolution: suspended
Priority: normal
Severity: minor
Target version: 4.03.0+dev / +beta1
Category: ~DO NOT USE (was: OCaml general)
Related to: #6610
Monitored by: @gasche "Julien Signoles" @hcarty @yakobowski

Bug description

The expression provided to compute the default value of optional arguments are pushed inside lambdas in the typedtree (cf Translcore.push_defaults). I assume this is done for performance purposes (efficient compilation of n-ary urried functions), but it can lead to surprising semantics (and I did not find it documented in the manual):

let f ?(x = print_endline "XXX"; 1) y = function z -> x + y + z;;
let g = f 2;;
g 1;;
g 2;;

will print XXX twice.

The default expressions are not pushed under "functions" with several branches:

let f ?(x = print_endline "XXX"; 1) = function Some y -> (function z -> x + y + z) | None -> (function z -> x + z);;
let g = f (Some 2);;

Here, "XXX" is printed directly (not each time g is called), contrary to:

let f ?(x = print_endline "XXX"; 1) = function Some y -> (function z -> x + y + z);;
let g = f (Some 2);;

Shouldn't we restrict the optimization to cases where the default expression is pure?

As a side note: Typeclass puts the "Default" mode on the generated let binding for default expressions on class arguments, but it is not recognized in a special way. I.e.:

class c ?(x = print_endline "XXX"; 1) y z = object method m = x + y + z end;;
let _ = new c 2

will print "XXX" immediately.

@vicuna
Copy link
Author

vicuna commented Apr 7, 2013

Comment author: @garrigue

Indeed, the documentation is lacking here.
The cause for this optimization is that the standard semantics would lead to a code size explosion with the native code compiler, in particular for LablTk.

Ideally indeed we would like to apply this transformation only when the expression is pure, but unfortunately there is no way to detect this in a meaningful way (we do not want any function application to disable this optimization).

I will fix the documentation to say that the evaluation of default expressions can be delayed arbitrarily until the real body of the function is reached, but the order of default expressions is preserved.

Also you are right that this optimization does not apply to object constructors, since their compilation path is completely different. It is not a good idea to use many optional arguments in an object constructor since this may hinder inheritance. Yet, it would not be overly difficult to apply it there too.

@vicuna
Copy link
Author

vicuna commented Apr 8, 2013

Comment author: @alainfrisch

Ideally indeed we would like to apply this transformation only when the expression is pure, but unfortunately there is no way to detect this in a meaningful way (we do not want any function application to disable this optimization).

I was under the impression that default expressions are often constants (such as [], "", 0) or identifiers (Location.none). Is the function application case really common?

It is not a good idea to use many optional arguments in an object constructor since this may hinder inheritance.

Can you elaborate on that?

@vicuna
Copy link
Author

vicuna commented Apr 8, 2013

Comment author: @garrigue

I was under the impression that default expressions are often constants (such as [], "", 0) or identifiers (Location.none). Is the function application case really common?

In LablGtk, there are many cases where the default is looked up in the runtime.
For instance, the default color palette.

Also a default may be defined with respect to another argument:

let f x ?(y=x+1) () = ...

It is true that this pattern is less frequent in ocaml than in olabl, where optional arguments didn't require a non-labeled argument to be after, but there are still occurrences of that.

It is not a good idea to use many optional arguments in an object constructor since this may hinder inheritance.

Can you elaborate on that?

I just meant that if you do that, you need to pass many optional arguments whenever you inherit, if you want to keep the customizability, whereas there are more clever ways to do that, such as value fields.
In LablGTK, my policy is to define the constructors as independent functions, outside of the class, as this avoids this problem, which stems from the fact ocaml only allows one constructor per class.

But I admit this is not a very good argument.
If there is some need, I'm ready to optimize this case too.

@vicuna
Copy link
Author

vicuna commented Apr 23, 2013

Comment author: @xclerc

Not the best idea I ever had, but I can remember using the
current semantics to automatically generate a new identifier
if none is provided.

@vicuna
Copy link
Author

vicuna commented Jun 18, 2013

Comment author: @damiendoligez

From the reference manual (section 6.7.1/function definitions):

Functions of the form fun ?lab:( pattern = expr0 ) -> expr are equivalent to
fun ?lab:ident -> let pattern = match ident with Some ident -> ident | None -> expr0 in expr
where ident is a fresh variable. When expr0 will be evaluated is left unspecified.

Note that last sentence: it is explicitly unspecified. Do we want to change the behaviour? Do we want to make the documentation more explicit?

@vicuna
Copy link
Author

vicuna commented Jun 18, 2013

Comment author: @alainfrisch

My fault, the documentation is indeed accurate.

I still have a preference for making the evaluation order well-defined by not pushing the evaluation unless the expression is provably pure. But it seems some projects rely heavily on the optimization, including for potentially impure expressions, so I won't push strongly in this direction.

@vicuna
Copy link
Author

vicuna commented Jun 20, 2013

Comment author: Julien Signoles

I think the current documentation is precise enough.

In Frama-C, there are a bunch of functions of the form
let f ?(project=current ()) other_arguments = ...

in which the result of (current ()) depends on when it is evaluating. However, such functions are never partially applied, so the behavior remains specified according to the current semantics.

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

2 participants