Add a with form #1

Open
opened 2023-12-14 01:14:11 +00:00 by kredati · 4 comments
kredati commented 2023-12-14 01:14:11 +00:00 (Migrated from github.com)

Based on Elixir's with: better than a separate bind for working with possibly-failing or complexly conditional chained results.

A with clause is a sort of backwards function clause: {pattern} <- {expression}. The expression is evaluated first, and then matched against the pattern.

with {
    {with-clauses}
    {expression}
}
else {
    {match-clauses}
}

Each with clause is evaluated, in order. The expression is evaluated first, and then the result is tested against the pattern. If the pattern matches, names are bound and used in the evaluation of the next clause's expression, which is then tested against a pattern, and so on. If everything works right, the expression is then evaluated.

If, however, at any given point, the result of the expression does not match against its pattern, the result is then sent to the else block, which contains bog-standard match clauses. The failing value is then tested against these clauses normally.

I don't quite know when we'd use this in Ludus, but having just written a parser and interpreter in Clojure, I am here to tell you this is the solution to no early return.

Based on Elixir's `with`: better than a separate `bind` for working with possibly-failing or complexly conditional chained results. A `with` clause is a sort of backwards function clause: `{pattern} <- {expression}`. The expression is evaluated first, and then matched against the pattern. ``` with { {with-clauses} {expression} } else { {match-clauses} } ``` Each `with` clause is evaluated, in order. The expression is evaluated first, and _then_ the result is tested against the pattern. If the pattern matches, names are bound and used in the evaluation of the next clause's expression, which is then tested against a pattern, and so on. If everything works right, the expression is then evaluated. If, however, at any given point, the result of the expression does _not_ match against its pattern, the result is then sent to the `else` block, which contains bog-standard `match` clauses. The failing value is then tested against these clauses normally. I don't quite know when we'd use this in Ludus, but having just written a parser and interpreter in Clojure, I am here to tell you this is the solution to no early return.
kredati commented 2023-12-14 01:37:42 +00:00 (Migrated from github.com)
For reference: https://hexdocs.pm/elixir/1.16/docs-tests-and-with.html#with
kredati commented 2023-12-14 04:49:42 +00:00 (Migrated from github.com)

Note: the Elixir with form does not require an else block, but rather simply returns the value from the first non-matching clause in the with block.

Note: the Elixir `with` form does not require an `else` block, but rather simply returns the value from the first non-matching clause in the `with` block.
kredati commented 2023-12-14 05:03:07 +00:00 (Migrated from github.com)

Syntactically, this should be a bit different than what we have here. All the with bindings are on the left-hand side, and then there's a single expression to execute. Elixir actually does with (<clause>, )+ do: <expr>.

There's no reason to use the backwards arrow here; the = operator between left and right models perfectly well the control flow.

So instead, you might instead try:

with { 
    (:ok, val1) = might_fail_1 ()
    (:ok, val2) = might_fail_2 () 
}
    then add (val1, val2)
    else { (:err, _) -> :oops }

What that does is allow fewer tokens, and a kind of consistency. if/then/else vs. with/then/else. Moreover, that obviates if let: we only need with here. And it should be easy enough to either have a single binding without braces, or multiple bindings with them.

Syntactically, this should be a bit different than what we have here. All the with bindings are on the left-hand side, and then there's a single expression to execute. Elixir actually does `with (<clause>, )+ do: <expr>`. There's no reason to use the backwards arrow here; the `=` operator between left and right models perfectly well the control flow. So instead, you might instead try: ``` with { (:ok, val1) = might_fail_1 () (:ok, val2) = might_fail_2 () } then add (val1, val2) else { (:err, _) -> :oops } ``` What that does is allow fewer tokens, and a kind of consistency. `if`/`then`/`else` vs. `with`/`then`/`else`. Moreover, that obviates `if let`: we only need `with` here. And it should be easy enough to either have a single binding without braces, or multiple bindings with them.
kredati commented 2023-12-15 02:09:04 +00:00 (Migrated from github.com)

The thing here is that this is a lovely model for a thing, but feels like it does not yet have much use in Ludus, since there isn't (yet) much that returns a result tuple. Wait to add this until it feels necessary for what we are doing in Ludus.

The thing here is that this is a lovely model for a thing, but feels like it does not yet have much use in Ludus, since there isn't (yet) much that returns a result tuple. Wait to add this until it feels necessary _for what we are doing in Ludus_.
scott self-assigned this 2023-12-15 21:25:22 +00:00
scott removed their assignment 2023-12-27 04:18:16 +00:00
scott added this to the 0.2.0 milestone 2024-05-19 19:52:59 +00:00
scott added this to the Completing the language project 2024-07-21 17:45:44 +00:00
Sign in to join this conversation.
No Milestone
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: twc/ludus#1
No description provided.