3 ms·
Honestly 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.
by ashton314 5y ago
Honestly 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.
- linkdd 5y agowith {:step_1, {:ok, value}} <- {:step_1, fn_call(...)} I also use this pattern a lot :) To be fair, I'm not using this library everywhere. Only when case/with become hard to read. Or when I have a pipeline like: fetch_list_of_resources() |> Result.or_else(fn _ -> [] end) # ignore the error |> Stream.filter(&check_condition/1) |> Stream.map(&delete_resource/1) |> Result.collect() # fails at the first error |> Result.unwrap!() # raise if error ASAP |> Enum.map(&log_deleted_resource/1) It is IMHO easier to read, refactor and debug (with some well-placed IO.inspect). Before this library, I was copy/pasting `then(fn ... end)`. I don't like to repeat myself :)