message-format-wg
message-format-wg copied to clipboard
Spec language on eager vs. lazy evaluation should be clarified
In #753 , @echeran brought up the example of a custom function getSystemTimeInMillisNow() that would return the current time.
In the spec intro we currently have the language:
"This specification does not require either eager or lazy expression resolution of message parts; do not construe any requirement in this document as requiring either."
Consider the following example (which formats to a string with the current time repeated twice... or does it?):
.local $x = {:getSystemTimeInMillisNow}
{{{$x} {$x}}}
In an eager implementation, this is guaranteed to print the same time twice.
In a lazy call-by-need implementation, where the right-hand side of $x is guaranteed to be evaluated at most once, it's also guaranteed to print the same time twice.
In a lazy call-by-name implementation, it may print out two different times.
In general, custom functions might have side effects observable by MF2, and getting the current time is a reasonable example of one. That means one can't assume that call-by-need and call-by-name are going to be equivalent.
I think all that's needed is an editorial note pointing out that depending on the contents of the custom function registry, call-by-need and call-by-name may be observably different and the implementor should think about which one is the least surprising.
One thought about what to say to users in the spec text: it's not just the laziness that users should consider from side-effecting functions (which is more about exactly when to evaluate), but also whether they want to memoize those functions (that is, cache the function result per each distinct set of input given), which would be more about giving consistent output across invocations of such a function.
Also thanks to @catamorphism since this well written issue follows from his comment over on #753:
This is indeed an important point, and not just an implementation detail IMO. ... maybe it should be noted somewhere in the spec that if custom functions with observable side effects exist, then careful thought should be put into lazy implementations.
One thought about what to say to users in the spec text: it's not just the laziness that users should consider from side-effecting functions (which is more about exactly when to evaluate), but also whether they want to memoize those functions (that is, cache the function result per each distinct set of input given), which would be more about giving consistent output across invocations of such a function.
That's right -- there are two different ways to implement laziness, one by using memoization (aka call-by-need) and one without, and the thing to explain is that the difference between the two is actually observable from a message, if certain custom function implementations exist.
(as contributor)
Note this text in the specification:
However, when an expression is resolved, it MUST behave as if all preceding declarations affecting variables referenced by that expression have already been evaluated in the order in which the relevant declarations appear in the message.
This suggests that the example message resolves the value of $x once, even though it is done lazily.
(chair 🎩 on)
We're at the point of releasing v46 Tech Preview. If you think a change is needed in the text of the specification, please make a PR.
Moving to 46.1
Does this need a PR?