~technomancy/fennel

2 2

Chained failure handling

Details
Message ID
<87wntsup0r.fsf@whirlwind>
DKIM signature
missing
Download raw message
One common problem in the Lua ecosystem is composing together successive
calls to functions which may fail and use the "return nil, msg" idiom to
indicate failure. One easy solution is to wrap all these calls in `assert`
to turn them into errors, but that's not always appropriate. Without
assert, in Lua you often see cases like this:

```lua
local ok, val = might_fail(a, b)
if not ok then
  return nil, val
end

local ok2, val2 = pcall(also_might_fail, val, c)
if not ok2 then
  return nil, val2
end

local ok3, val3 = finalize(val2, c)
if not ok3 then
  return nil, val3
end
```

...repeated ad nauseum.

I think in Fennel we can do better than this. Of course, you can already
write a macro for yourself to abstract away this kind of chaining. But
I'm wondering if it would be appropriate to include such a macro in the
language itself since this kind of error handling is so common.

One potential source of inspiration is Elixir's `with` special form:

https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1

>    opts = %{width: 10, height: 15}
>    with {:ok, width} <- Map.fetch(opts, :width),
>         {:ok, height} <- Map.fetch(opts, :height) do
>      {:ok, width * height}
>    end
>    {:ok, 150}
>
> If all clauses match, the do block is executed, returning its
> result. Otherwise the chain is aborted and the non-matched value is
> returned.

The Fennel equivalent might look like this:

```fennel
(-match-> (might-fail data)
  data2 (pcall also-might-fail data2 x)
  data3 (finalize foo data3))
```

Where each of the functions `transform`, `second-step`, and `finalize`
can return either a successful value or nil, msg. In the case of a
failure at any point, the entire form evaluates to nil, msg.

I like this because it kind of blends `match` and `->` in that it has
the pattern-like behavior but also the method of chaining together
successive calls (but not the "inserting forms into an existing call"
part of the arrow macro).

However, this could be considered overkill; the full power of pattern
matching is not really needed here since you know that the error case
will always be the same: nil followed by some more values.

One alternative we considered was changing the existing `-?>` form so
that in cases where you found a nil, it would return all the values
instead of just the nil value. Unfortunately because multiple values are
not really first-class in Lua, this is not possible to do without
introducing some extra runtime overhead.

But maybe that's not a big deal? This form could become more useful, and
folks who wanted a faster version of it could just implement their own
macro; it's only a few lines.

This is strictly less flexible than the matching equivalent because the
each value from the previous form can only be in the first position or
last position, but it's maybe more consistent with what we already
have. Or we could introduce a new macro which behaves like `-?>` except
it does handle all the values?

Interested in hearing peoples' thoughts on this.

-Phil
Details
Message ID
<CAAKhXobUxt3zVjRvQxGdxQugWQG3pq9YnW-AAU0UP6uq07i8fQ@mail.gmail.com>
In-Reply-To
<87wntsup0r.fsf@whirlwind> (view parent)
DKIM signature
pass
Download raw message
> The Fennel equivalent might look like this:
>
> ```fennel
> (-match-> (might-fail data)
>   data2 (pcall also-might-fail data2 x)
>   data3 (finalize foo data3))
> ```
>
> Where each of the functions `transform`, `second-step`, and `finalize`
> can return either a successful value or nil, msg. In the case of a
> failure at any point, the entire form evaluates to nil, msg.

This also reminds me of Clojure's `cond->`, but instead of conditions,
we do pattern matching. Maybe just `match->` :) (without the `-` at
the beginning)

I also wonder what exactly does this macro expands to? Is it a nested
set of matches? If so, how error case is handled?


-- 
Andrey Listopadov
Details
Message ID
<367269490e3dea5c4607b22adc0965209ab1e97d.camel@michel-slm.name>
In-Reply-To
<CAAKhXobUxt3zVjRvQxGdxQugWQG3pq9YnW-AAU0UP6uq07i8fQ@mail.gmail.com> (view parent)
DKIM signature
pass
Download raw message
On Sun, 2021-03-28 at 11:31 +0300, Andrey Listopadov wrote:
> > The Fennel equivalent might look like this:
> > 
> > ```fennel
> > (-match-> (might-fail data)
> >   data2 (pcall also-might-fail data2 x)
> >   data3 (finalize foo data3))
> > ```
> > 
> > Where each of the functions `transform`, `second-step`, and
> > `finalize`
> > can return either a successful value or nil, msg. In the case of a
> > failure at any point, the entire form evaluates to nil, msg.
> 
> This also reminds me of Clojure's `cond->`, but instead of
> conditions,
> we do pattern matching. Maybe just `match->` :) (without the `-` at
> the beginning)
> 
> I also wonder what exactly does this macro expands to? Is it a nested
> set of matches? If so, how error case is handled?
> 
Who knows the Maybe Monad is so trivially implemented in a language
with multiple return values!

Clojure's cond-> does remind me of Haskell's do notation (though I've
not used either for a while now and might be mistaken here).

Best regards,

-- 
Michel Alexandre Salim
profile: https://keyoxide.org/michel@michel-slm.name
Reply to thread Export thread (mbox)