<div dir="ltr"><div dir="ltr"><div>My initial thoughts about an approach where <~ is not lexically bound by any construct is that it may make moving code around harder.</div><div><br></div><div>For example, if you have this function:</div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">foo() -></span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, Value} <~ file:read_file("somefile.erl"),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> Another = do_something_with(Value),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> do_something_else(Another).</span></div><span style="font-family:monospace"></span><div><br></div><div><div>If you are going to move the first two lines to a private function then the automatic extraction does not work:<br></div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">foo() -></span></div><div style="margin-left:40px"><span style="font-family:monospace"> Another = bar(),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> do_something_else(Another).</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">bar() -></span><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, Value} <~ file:read_file("somefile.erl"),</span><span style="font-family:monospace"></span><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> do_something_with(Value).</span></div><span style="font-family:monospace"></span></div><div><br></div><div>You have to do this:<br></div><div><br></div><div><div><div style="margin-left:40px"><span style="font-family:monospace">foo() -></span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, Another} <~ bar(),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> do_something_else(Another).</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace">bar() -></span><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, Value} <~ file:read_file("somefile.erl"),</span><span style="font-family:monospace"></span><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> do_something_with(Value).</span></div><span style="font-family:monospace"></span></div></div><div><br></div><div>Which is doable but not obvious because the scope <~ applies to is not immediately clear. I think something like this:</div><div><br></div><div><div style="margin-left:40px"><span style="font-family:monospace">foo() -></span></div><div style="margin-left:40px"><span style="font-family:monospace"> begin<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, Value} <~ file:read_file("somefile.erl"),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> Another = do_something_with(Value),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> do_something_else(Another)</span></div><div style="margin-left:40px"><span style="font-family:monospace"> end.</span></div><span style="font-family:monospace"></span></div></div><div><br></div><div>provides a clearer indicator of the scope and more clues that moving code around requires extra work. The other benefit of having a delimiter is that you can include else clauses that Fred mentioned, and I believe you will quickly find out they are a must have. Imagine you want to perform many operations that may fail and, if they do, you want to raise an error. Without else, you have to do this:<br></div><div><br></div><div><div style="margin-left:40px"><span style="font-family:monospace">change_and_backup(File) -></span></div><div style="margin-left:40px"><span style="font-family:monospace"> Res =<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> begin<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, Value} <~ file:read_file(File),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> NewValue = do_something_with_value(Value),<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> ok <~ file:write_file(File ++ ".backup", Value),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> ok <~ file:write_file(File, NewValue),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, NewValue}<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> end,</span></div><div style="margin-left:40px"><span style="font-family:monospace"><br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> case Res of</span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, NewValue} -> NewValue;</span></div><div style="margin-left:40px"><span style="font-family:monospace"> {error, Reason} -> erlang:error({backup_error, Reason}, [File])</span></div><div style="margin-left:40px"><span style="font-family:monospace"> end.<br></span></div></div><div class="gmail_quote"><div dir="ltr" class="gmail_attr"><br></div><div class="gmail_attr">With else, you do this:</div><div class="gmail_attr"><div><br></div><div><div style="margin-left:40px"><span style="font-family:monospace">change_and_backup(File) -></span></div><div style="margin-left:40px"><span style="font-family:monospace"> begin<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> {ok, Value} <~ file:read_file(File),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> NewValue = do_something_with_value(Value),<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> ok <~ file:write_file(File ++ ".backup", Value),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> ok <~ file:write_file(File, NewValue),</span></div><div style="margin-left:40px"><span style="font-family:monospace"> NewValue<br></span></div><div style="margin-left:40px"><span style="font-family:monospace"> else</span></div><div style="margin-left:40px"><span style="font-family:monospace"> {error, Reason} -> erlang:error({backup_error, Reason}, [File])</span></div><div style="margin-left:40px"><span style="font-family:monospace"> end.</span></div></div><div><br></div><div>Note the compiler can raise if else is given but no <~ is used.</div><div><br></div><div>If I remember correctly, Elixir's with originally started without else, but else was added in the next release because it was seen as a very clear extension of the original mechanism. I have included some examples from the Elixir codebase where we use "with". I think the first example is really clear on the benefit of usng "with" to perform validation, compared to something like using try/catch:<br></div><div><br></div><div>* <a href="https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/lib/dynamic_supervisor.ex#L355-L361">https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/lib/dynamic_supervisor.ex#L355-L361</a></div><div>* <a href="https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/lib/file.ex#L560-L574">https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/lib/file.ex#L560-L574</a></div><div>* <a href="https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/diff.exs#L83-L104">https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/diff.exs#L83-L104</a></div><div>* <a href="https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/lib/version.ex#L536-L546">https://github.com/elixir-lang/elixir/blob/272303e558286dff35ace00eada6d2030431874d/lib/elixir/lib/version.ex#L536-L546</a></div></div><div class="gmail_attr"><br></div><div class="gmail_attr"><div>Finally, another potential benefit of having a explicit delimiter is that you don't need a new operator as you can re-use <- if you want to. Especially because <- inside a comprehesion already has a "soft match" semantics (i.e. it doesn't raise if it doesn't match).<br></div></div><div dir="ltr" class="gmail_attr"><br></div><div dir="ltr" class="gmail_attr">On Thu, Oct 22, 2020 at 12:53 PM Kenneth Lundin <<a href="mailto:kenneth@erlang.org">kenneth@erlang.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div>See embedded comments<br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Oct 15, 2020 at 4:58 PM Fred Hebert <<a href="mailto:mononcqc@ferd.ca" target="_blank">mononcqc@ferd.ca</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr"></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Oct 15, 2020 at 3:31 AM Kenneth Lundin <<a href="mailto:kenneth@erlang.org" target="_blank">kenneth@erlang.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr"><div><div><div>
<div>
<div style="color:black;font-size:12pt;font-family:Calibri,Arial,Helvetica,sans-serif">We welcome initiatives like this and are positive to revisit this.</div>
<div style="color:black;font-size:12pt;font-family:Calibri,Arial,Helvetica,sans-serif">
A proposal for something closer to <i>with</i> in Elixir looks interesting.
</div></div></div></div></div></div></div></blockquote><div><br></div><div>Alright. So before I get into the big details, here are a few things / variables I'm considering if we are to redesign this. <br></div><div><br></div><div>I'm labelling them as below into proposal rewrites 1 through 3, some with variants. Let me know if some of them sound more interesting.<br></div><div> <br>First, dropping the normative return values of <span style="font-family:monospace">ok | {ok, T} | {error, R}</span>:</div><div><br></div><div><span style="font-family:monospace">begin<br> {ok, A} <~ exp(),<br> {ok, B} <~ exp(),<br> {ok, A+B}<br>end.</span><br></div><div><br></div><div>This has interesting impacts in some of the examples given in the EEP, specifically in that <span style="font-family:monospace">_ <~ RHS</span> now means as much as <span style="font-family:monospace">_ = RHS</span>, but also that it allows rewriting some forms. The RFC looked at expressions such as:</div><div><br></div><div><span style="font-family:monospace">backup_releases(Dir, NewReleases, Masters, Backup, Change, RelFile) -><br> case at_all_masters(Masters, ?MODULE, do_copy_files, [RefFile, [Backup, Change]]) of</span></div><div><span style="font-family:monospace"> ok -></span></div><div><span style="font-family:monospace"> ok;<br></span></div><div><span style="font-family:monospace"> {error, {Master, R}} -><br> remove_files(Master, [Backup, Change], Masters)<br> end. </span><br></div><div><br></div><div>Could now be written the following way:<br></div><div><br></div><div><span style="font-family:monospace">backup_releases(Dir, NewReleases, Masters, Backup, Change, RelFile) -><br> begin<br> {error, {Master, R}} <~ at_all_masters(Masters, ?MODULE, do_copy_files, [RefFile, [Backup, Change]]), <br> remove_files(Master, [Backup, Change], Masters)<br> end.</span><br></div><div><br></div><div>Which looks a bit funny because the main path is now the error path and the happy-path is fully removed from the situation.</div><div><br></div><div>In any case, this is the most minimal rework required, has some edge cases pointed out by the EEP already (a match for <span style="font-family:monospace">{ok, [_|_]=L} <~ RHS</span> can end up doing a short return for <span style="font-family:monospace">{ok, Tuple}</span> for example, and interfere with expected values). I'll call this <b>proposal rewrite 1. </b><br></div><div><br></div><div>We can't avoid the above escaping mechanism without normalizing over what is an acceptable or unacceptable good match value, and proposal rewrite 1 makes this impossible. This requires adding something akin to the <span style="font-family:monospace">else</span> construct in the elixir <span style="font-family:monospace">with</span>:</div><div><br></div><div><span style="font-family:monospace">%% because of `f()', this returns `{ok, "hello"}' instead of `{ok, <<"Hello">>}'<br>%% or a badmatch error.<br>f() -> {ok, "hello"}.<br>validate(IoData) -> size(IoData) > 0.<br>sanitize(IoData) -> string:uppercase(IoData).<br><br>fetch() -><br> begin<br> {ok, B = <<_/binary>>} <~ f(),<br> ok <~ validate(B),<br> {ok, sanitize(B)}<br> end.</span></div><div><span style="font-family:monospace"><br></span></div><div><span style="font-family:monospace">%% Only workaround:<br>fetch_workaround() -><br> begin<br> {ok, B = <<_/binary>>} <~ f(),<br> ok <~ validate(B),<br> {ok, sanitize(B)}<br> else<br> {ok, [_|_]} -> ...<br> end.</span></div><div><span style="font-family:monospace"><br></span></div><div>This format might work, but requires introducing new extensions to the <span style="font-family:monospace">begin ... end</span> form (or new things like<span style="font-family:monospace"> maybe ... end</span>), regardless of terms. In terms of semantics, a <span style="font-family:monospace">catch</span>, <span style="font-family:monospace">of</span>, or <span style="font-family:monospace">after</span> block might reuse existing keywords but wouldn't be as clear in terms of meaning. Specifically addressing this requirement that comes from relaxing semantics for proposal 1 is <b>proposal rewrite 2</b>. <b>Variant A</b> would be to keep it as described above, and <b>Variant B</b> would include the potential options with other alternative keywords and blocks.<br></div><div><br></div><div>Either way, dropping the pattern and changing constructs maintains the overall form and patterns described in the EEP. They however still keep <span style="font-family:monospace">LHS <~ RHS</span> as a special expression type that is always contextual, which was pointed out to be a thing the OTP team did not like. Making it apply everywhere is a particularly tricky bit, but I think it might be possible.</div><div><br></div><div>First, we need to define where a free-standing <span style="font-family:monospace">LHS <~ RHS</span> is going to return. If it's free-standing it can't be a sort of macro trick for a case expression, and it can't also be based on a throw, since throws can't clearly disambiguate the control flow required for this construct vs. random exceptions people could be handling at lower levels.I've seen in EEP-52 that there is a core-erlang construct as a letrec_goto, and using it we might be able to work with that. <br></div><div><br></div><div>We'd first have to choose which scope nested expressions would need to return to:</div><div><br></div><div> <span style="font-family:monospace"> a() -><br> V = case f() of<br> true -> <br> ok <~ g(),<br> h();<br> false -><br> {ok, X} <~ i(),<br> k(element(2, {ok, Y} <~ j(X)))<br> end,<br> handle(V).</span><br></div><div><br></div><div>This is an interesting test bed for some possible execution locations where the new operator could be bound. We could pick:</div><div><ul><li>shortcut the lexical scope: since case expressions and any other construct share the parent lexical scope and can export variables, we would have to expect that <span style="font-family:monospace">{ok, X} <~ i()</span> implies that <span style="font-family:monospace">a()</span> itself can return <span style="font-family:monospace">i()</span>'s value directly if it doesn't strictly match the form, regardless of how deeply nested we are in the conditional. This is unlikely to be practical or expected to people, but would nest appropriately within funs. It may have very funny effects on list comprehensions when used as part of generators and that likely will need special treatment.</li><li>shortcut to the parent control flow construct / end of current sequence of expression: I don't know how to word this properly, but the idea would be to limit the short-circuit return to the prior branching or return point in the language. This means that <span style="font-family:monospace">{ok, X} <~ i()</span> failing implies that <span style="font-family:monospace">V</span> gets bound to the return value of <span style="font-family:monospace">i()</span>, and similarly for the return value of <span style="font-family:monospace">j()</span> if it were to fail. Upon seeing a <span style="font-family:monospace">LHS <~ RHS</span> expression, the compiler would need to insert a label at the end of the current sequence of expressions (which may conveniently going to be explained as "all of the current expressions separated by a comma <span style="font-family:monospace">[,]</span>), and do a conditional jump to it if the expression fails to match. If it works it keeps chugging along, and the last expression in the sequence can just jump to the same label with the identified return value. To my understanding, this wouldn't interfere with LCO nor require more stackframes than any other conditional would ever require.</li></ul></div></div></div></blockquote><div>I think the middle bullet (above this) is the most interesting. The LHS <~ RHS expression should be thought of as MATCH_OR_BREAK or MATCH_OR_RETURN a conditional return as I think you mention somewhere.</div><div>The scope to break out from is function clause, begin/end, case clause and probably something else which I have not thought of yet.</div><div>If we introduce this maybe it is strange to not introduce an unconditional return as well.</div><div>Will take a closer look into the 'with' construct in Elixir and see if there is anything more we could copy.</div><div>Note, I have discussed this briefly with some of the OTP team members, see this as an initial view point not written in stone.<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><div><ul><li>Something else I haven't thought of<br></li></ul><div>I also assume that none of these expressions would ever be valid in guards since they can't do assignment today. I'm also unsure of whether letrec_goto can use a label with arguments in what to execute (which would let it carry/return a variable), but I'm waiting to do research on that on whether this idea looks good or not to the OTP team. I think the lexical scope option is unacceptable. I call the sequence of expressions approach <b>proposal rewrite 3</b>. This is much more ambitious and could have a ton weirder unexpected effects, but it drops all pretense and introduces a new operation type/control flow mechanism (which is comparable to a conditional <span style="font-family:monospace">return</span> somewhat scoped like a <span style="font-family:monospace">continue</span> in an imperative language) rather than a new operator within a bound construct.<br></div><div><br></div><div>An interesting <b>Variant B</b> for this one would be that since we make the expression general, we could change the <span style="font-family:monospace">LHS <~ RHS</span> expression to instead be <span style="font-family:monospace">LHS <- RHS</span> expression; after all, there is no unwrap involved anymore, and the logical handling of this thing is now much closer to what you'd see in a list comprehension such as <span style="font-family:monospace">[handle(X) || {ok, X} <- [i()]]</span>.</div><div><br></div><div>Let me know what you think about these.<br></div></div></div></div></blockquote><div><br></div><div>/Kenneth, Erlang/OTP Ericsson <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><div><div></div></div></div></div>
_______________________________________________<br>
eeps mailing list<br>
<a href="mailto:eeps@erlang.org" target="_blank">eeps@erlang.org</a><br>
<a href="http://erlang.org/mailman/listinfo/eeps" rel="noreferrer" target="_blank">http://erlang.org/mailman/listinfo/eeps</a><br>
</blockquote></div></div>
_______________________________________________<br>
eeps mailing list<br>
<a href="mailto:eeps@erlang.org" target="_blank">eeps@erlang.org</a><br>
<a href="http://erlang.org/mailman/listinfo/eeps" rel="noreferrer" target="_blank">http://erlang.org/mailman/listinfo/eeps</a><br>
</blockquote></div></div>