6 ms·
Show HN: Result monad for Elixir inspired by Rust Result type
- dmitriid 5y agoNo. I've been burned by this in other projects (see e.g. https://github.com/0xYUANTI/tulib/blob/master/include/types.hrl https://github.com/0xYUANTI/tulib/blob/master/include/types....) You code will soon be a mish-mash of thunk(lift(unlift(thunk(err))))s These things have to be a part of the language, or not at all
- linkdd 5y agoOr thanks to elixir's pipeline operator: err |> thunk() |> unlift() |> lift() |> thunk() Which is already more readable. And I choosed the Rust terminology for a reason, I think and_then/or_else is way clearer than fmap/bind. But I agree, I would love for Elixir to have a builtin Result type.
- aeontech 5y agoI don’t even know Elixir at this point, but I have to say I _love_ the example list in the README! So nice!
- sph 5y agoThe documentation is generated with ex_doc, a core package. Examples in the documentation are automatically tested by the testing library ex_unit, so you get examples for your users which are automatically tested for correctness. It's a pretty awesome feature indeed. Though to be fair the examples in this library aren't using the correct syntax to be tested.
- linkdd 5y agoI should take a look more closely at the doctest feature indeed :)
- linkdd 5y agoThank you, I try my best to come up with good documentation so I don't need to go read the source code :) Also that's what I love about Erlang/Elixir, you don't need to know the language to be able to read code written in it.
- miccah 5y agoAs a Rustacean currently learning Elixir, I found myself wanting this exact functionality. Kudos for making this, I'll definitely use it!
- ashton314 5y agoSee my reply elsewhere; you might be better off with using `with` in conjunction with the `{:ok, value}` or `{:error, "message"}` convention. That's more or less what this package does. Welcome to Elixir! My company uses Elixir extensively, but I'm trying to get us to use some Rust for some NIFs and some other projects that would benefit from some performance. Both languages are really great and I derive great joy from using them. :)
- linkdd 5y agoThanks :) I was copy/pasting bits of this code in multiple projects, this quickly became annoying.
- ashton314 5y agoHonestly not sure how I feel about this. It's pretty small, and I feel like most of the time you're better off just using some of the existing conventions—e.g. using `with` to get a poor man's implementation of `>>=/bind`, and use the `{:ok, value()} | {:error, "Boom"}` pattern: with {:ok, fh} <- maybe_open_file("filename.foo"), {:ok, line} <- maybe_read_line(fh), :ok <- try_close_fh(fh) do {:line_received, line} end This gets you the same kind of flow, and is much more idiomatic Elixir. That being said, I have wished this convention could be more formalized. If you want to use this code base across your platform, knock your socks off. Elixir I feel like hits a sweet spot between flexibility+power and readability. Strong community conventions seem to help a lot as well. This package does look nice; I appreciate the README and the docs that they put together—nice to see some thought and care put into a little project like this. :)
- linkdd 5y agoThe with statement will leave the pipeline as soon as the first error, while a chain of and_then/or_else allow you to continue the execution of the pipeline. Which is what I wanted, example: some_operation() |> and_then(&some_other_operation/1) |> or_else(&some_error_handling/1) |> and_then(&another_operation/1) The first and second step can fail and be handled by the third step, then continuing normally with the fourth step. Doing this with a with statement is awkward (unless I've been doing it wrong). Thanks for the feedback anyway :)
- ashton314 5y agoAh, I missed the `or_else` function. Normally, you'd do something like: with ... do ... else {:error, _} = err -> # ... handle error here ... err # propagate the error end But if you need to handle errors at different points of the pipeline differently, it can get pretty unwieldy. (I've wrapped the function calls in a tuple like with {:step_1, {:ok, value}} <- {:step_1, fn_call(...)} so I could dispatch on that tuple in the `else` block, but I think your solution is cleaner than that—especially if you need some kind of recovery that lets you go on with the normal computation. :) So if that's what you need to work with, then I don't think you're doing it wrong.
- bertylicious 5y agoNice, every language should have a Result/Either Monad. Why not make it a functor too and add map()? ok(1) |> map(fn v -> v + 1 end) # ok(2) Of course you can also make it a bifunctor by implementing bimap(). And what about filter()?
- linkdd 5y agoCould be planned for the 0.4.0 :) Honestly it was made just to scratch an itch I had for a while.
- pjmlp 5y agoWhich was inspired by OCaml Result Type. https://ocaml.org/api/Stdlib.html#1_Resulttype https://ocaml.org/api/Stdlib.html#1_Resulttype
- Munksgaard 5y agoAlgebraic datatypes, which naturally begets Option and Either, predate OCaml.
- pjmlp 5y agoI am fully aware of it, I was just making the point that it wasn't invented by Rust as the title suggests, and first versions of Rust were written in OCaml, hence why I chose that ML flavour for the example. If you want to go pedatic we can start discussing the type theory papers they appear on.
- linkdd 5y agoWhen I say "inspired by Rust Result type", I mean the function names. I could implement map/fmap/bind/lift/unlift/return/... But I choosed and_then/or_else/unwrap/...