Has the `with` construct replaced the need for error handling in pipelines

Last year I wrote a library for error handling in elixir pipelines OK.
There were several discussions around this topic in the google group and I was certainly not the only one to suggest a solution like this.

The idea is that the pipeline continues if the return value is {:ok, something} and stops and returns if the return value is {:error, reason}. It allows you to write code like the sample below.

def get_employee_data(file, name) do
  {:ok, file}
  ~>> &File.read/1
  ~>> &Poison.decode/1
  ~>> &Dict.fetch(&1, name)
end

I find that I still use this myself, but that perhaps I should be using the with construct more.
My question is has with reduced your use of pipelines in general?
This would seam note worthy as the pipes are of a feature with new developers are very keen on.

1 Like

Heh, I link your ā€˜okā€™ library at times as an example of doing things. For context there are other libraries for this style of piping and with style error handling. There is happy (my personal favorite for the general case, entirely replaces with via happy_path, but with extra features like a default else case for things not handled in the main else area and such, and works on older Elixirā€™s), ok_jose, which is kind of like your ā€œokā€ except instead of adding a new inline operator it uses the normal pipe operator, you just have to finish your pipeline with |> ok, which thanks to how it compiles all the entire previous pipeline gets passed in to ok as a macro to do its work, and lastly the new-comer (just released) exceptional, which has 3 different ways of error handling, the usual tagged tuples like ok/ok_jose/happy/with support for error handling, exceptional handling, and a new method of returning the wanted value or returning an exception object (not raising it, returning it) to handle the successful/error cases, an interesting looking style I need to look closer at.

Now I barely use with itself because I use the default else case of happy_path/happy_path! excessively (great for handling generic errors in phoenix like bad params, bad database lookup, etcā€¦ etcā€¦), but considering happy_path and with do the same kind of use-case in an almost identical way then I do not really ever use things like ok/ok_jose/etc but instead I do pattern match out the tuple, this is because I have my default error case handle almost everything (case-specific things are handled via a case branch, that then falls back to the generic error handler) and returning a message saying the user passed a bad argument or there is no row with that ID or whatever is a nicer message.

5 Likes

Also I spoke a bit of error handling styles at: Error handling styles

1 Like

Are these two things not orthogonal? I am still an Elixir newb, but I use the with statement generally as follows (real-world, not foo to show my point):

with \
  {:ok, fork_info} <- fork(Helper.get_ib_gib!(ib, gib), identity_ib_gibs, dest_ib, opts),
  {:ok, :ok} <- IbGib.Data.save(fork_info),
  {:ok, fork} <- Expression.Supervisor.start_expression({fork_info[:ib], fork_info[:gib]}),
  {:ok, new_pid} <- contact_impl(fork, state) do
  {:ok, new_pid}
else
  {:error, reason} -> {:error, reason}
  error -> {:error, "#{inspect error}"}
end

So each clause in the with statement is not necessarily passing in the result directly into the proceeding functionā€™s first parameter. Sometimes, I will be using a result of the with statement two or three clauses down. The pipe, on the other hand, I thought was more for a simpler ā€œchain of eventsā€ that are constructed specifically to be used in a pipeline. But after reading your post, it seems obvious that a happy pipeline would definitely be awesome.

I recently came across @Onor.io 's blog about (Railway Oriented Programming with Elixir](https://onor.io/2015/08/27/railway-oriented-programming-in-elixir/). At the end of his blog post, he references a different great blog post, in the comments of which there were two other implementations published: MonadEx by rob-brown, and plumber_girl by ruby2elixir.

Here is a summary list of just what Iā€™ve found:

  1. OK
  2. happy_path
  3. MonadEx
  4. plumber_girl
  5. ok_jose
  6. exceptional

So Iā€™d it definitely looks like there is still a use case for OK and others for happy pipelining. But with is very useful and less restrictive as to the needing to input the previous result into the first param of the next clause, and it doesnā€™t require the additional learning curve of another library for onboarding contributors.

Is there a difference between the happy path default else case and just having the else error -> handle_error in a with statement?

I think there is none from what I saw in examples, but iā€™m curious if I am wrong. Because for now I see no advantages (for me) of happy_path over with.

1 Like

Well, the tag feature looks pretty neat. I havenā€™t had a use case for it yet, but if I were to need to differentiate between failure cases in the path, it could be useful. :thinking:

Not really, that is the point though, it just simplifies that call so I do not have to even put an else branch in almost all of the functions (I think I have 4, out of over a 2 hundred happy_caseā€™s). I heavily use its ā€˜@taggingā€™ abilities too though, plus I like doing = instead of <- as I like it to look more ā€˜normalā€™.

1 Like

Thatā€™sā€¦ hmmā€¦ yep, thatā€™s so true :smiley:

Thanks for the thorough reply. it seams like with an ok are certainly very close to each other. enough that people picking a favorite is an important consideration for there use.

Also thanks for linking exceptional I had not yet found that one.

I think it is interesting that you say there is definitely a usecase, I donā€™t yet feel very strong with elixir macros but I would like to update ok so it looks like a proper pipeline rather than have to take anonymous functions.

i.e.

# replace
~>> &Dict.fetch(&1, name)
# with
~>> Dict.fetch(name
1 Like

The ok_jose and the author that did exceptional has other libraries (look in their public github profile) that also has support for handling such things the way you wish as well (so does exceptional itself as it is more of a union of other formats).

But yes, the syntax you described is entirely possible, not hard either. :slight_smile:

So after this discussion I have decided to play with this library again. There is now proper documentation available.

2 Likes

Ah cool. :smile:

I had forgotten to mention it here, but I had a use case for this only a couple days after this discussion. I downloaded OK, but I ended up reverting back to just with even though I would have preferred a pipe operator of some kind:

# Build the plan (_simple_ happy pipe would be awesome)
{:ok, plan} <- TB.plan(identity_ib_gibs, "[src]", opts),
{:ok, plan} <- TB.add_fork(plan, "fork1", dest_ib),
{:ok, plan} <- TB.add_rel8(plan, "rel8_2_src", "[plan.src]", ["instance_of"]),
{:ok, plan} <- TB.yo(plan),

This is one of the occurrences in current code (including the comment ;)) where I wanted a happy pipe-ish thing. I mean it looks decent just like this, but a pipe would have been better! I tried both OK and happy and I just wasnā€™t happy with either (pardon the pun :neutral_face:). I believe that the anonymous syntax using & didnā€™t ā€œworkā€ for me, making it unreadable, and the full-fledged fn syntax is too verbose. Are you going to give it a go trying to remove the anonymous syntax :question:

I donā€™t remember what I didnā€™t like about happy piping. :thinking: Btw, I just saw yesterday that the happy author has made another tiny macro called happy_with (fun name) recently that basically removes the commas, but keeps the rest of the with syntax.

1 Like

I am certainly going to try and get rid of the need for the anonymous function syntax. I watched a talk about metaprogramming elixir at pixels.camp yesterday and was inspired to give it a go. unfortunately I been reading through the source code for the \> macro and it looks no trivial.

Hopefully later this week Iā€™ll have something to show for my efforts

1 Like

Anonymous function syntax?

At the moment you have to do this

{:ok, file}
  ~>> &File.read/1

The & makes a named function anonymous and is necessary because calling named functions and anonymous functions are different in elixir.
I would like to be able to to this.

{:ok, file}
~>> File.read

@ibgib I have worked out how to get rid of the requirement on using anonymous function and now have a pull request open on a syntax which is much closer to the native \>.

Any feedback would be appreciated.

1 Like

Awesome :023: This got me to do a bit of Sunday coding :keyboard: :desktop:

Iā€™ve already created an issue on my ibgib repo and implemented a branch with the changes. It helped me in my refactoring that I have been planning on doing, and I continue to do. Here is an example of a newly refactored factory function now:

# Old code, requires a `with` statement
# {:ok, plan} <- TB.plan(identity_ib_gibs, "[src]", opts),
# {:ok, plan} <- TB.add_fork(plan, "fork1", dest_ib),
# {:ok, plan} <- TB.add_rel8(plan, "rel8_2_src", "[plan.src]", ["instance_of"]),
# {:ok, plan} <- TB.yo(plan),

# Now...
def instance(identity_ib_gibs, dest_ib, opts) do
  {:ok, identity_ib_gibs}
  ~>> TB.plan("[src]", opts)
  ~>> TB.add_fork("fork1", dest_ib)
  ~>> TB.add_rel8("rel8_2_src", "[plan.src]", ["instance_of"])
  ~>> TB.yo
end

Perhaps with only a couple of these, it wouldnā€™t make a huge difference, but I will be expanding on these factory functions to include more ā€œcompositeā€ plan functions like this instance/3. So it makes it especially nice to be able to clean :shower: them up so concisely. I will respond on your open PR regarding the other notes on the upgrade! :smile:

Thanks for your hard work! :smiley:

Iā€™ve also responded on your PR and given the GitHub ā€œAdd a reviewā€ functionality a spinā€¦thatā€™s pretty cool! :smile: