Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OK.with else pattern matching #10

Closed
ghost opened this issue Feb 20, 2017 · 8 comments
Closed

OK.with else pattern matching #10

ghost opened this issue Feb 20, 2017 · 8 comments

Comments

@ghost
Copy link

ghost commented Feb 20, 2017

I am just now starting to look at incorporating the OK.with block, and I really like the reduction of noise it gives, but I am unsure how to handle the else block. In my usage, 95% of the time I use a default error handler like so:

with(
  1..
  2..
) do
  {:ok, result}
else
  error -> default_handle_error(error)
end

I try to make all of my errors return in the form of {:error, reason}, but ya never know right. Sometimes I need to pattern match on other errors.

Are you working on something parallel to elixir's else pattern matching in with statements, or is this functionality already implemented?

@CrowdHailer
Copy link
Owner

I haven't yet implemented an else part of with. Although it shouldn't be hard.

I think that when I do I will probably be strict such that it only works with {:error, reason}. For my own purposes I always wrap my functions as I need to return only those two values. Doing so is the only way the the return can be fully treated as an implementation of a result monad, which is necessary for some of the niceties in this library.

however at the moment you can do the following.

OK.with do
  a <- func1()
  b <- func2()
  c <- func3(a, b)
end
|> case do
  {:ok, value} ->
    value
  {:error, _reason} ->
    its_ok_ill_return_a_default_value
end

admittedly a bit messy. but I have used it a few times and it should refactor nicely when the else option arrives

@ghost
Copy link
Author

ghost commented Feb 20, 2017

@CrowdHailer Thanks for the quick response 👍

I also am very strict (as strict as I can be about anything 😉) about returning {:error, reason}, but nonetheless sometimes it is very useful. For instance, with "tagging" pattern matches when you actually need to know which one failed. Although this is more of an edge case and a fallback to the standard Elixir with could be used instead.

I agree that the piped case statement is a bit messy, as I've seen that used in code before the else pattern matching became available with Elixir's with. I think that using that would then obfuscate the meaning compared to the rest of my code base and the gains from the noise reduction would be negated.

But I just think it's so awesome the noise reduction! Ah well. I'll be looking forward to seeing what you come up with.

Thanks 😄

@CrowdHailer
Copy link
Owner

I'm 90% sure the API will look like

OK.with do
  a <- func1()
  b <- func2()
  func3(a, b)
else
  :reason1 ->
    {:ok, :actually_ok_have_a_default}
  :reason2 ->
    {:error, :still_an_error_but_i_have_more_info_that_i_want_to_add_here}
end

Unless there are any better suggestions. I'm pretty busy at the moment so will see when can get round to it.

@ghost
Copy link
Author

ghost commented Feb 20, 2017

I'm not saying I have any better suggestions, I'm just talking this out! I really like your approach and your reasoning, and I'm just not sure on this one aspect. I totally understand about being busy on multiple fronts. 😅

As for this though, just to be explicit, in your example it would be a tuple {:error, :reason1} to match against the :reason1? (probably doesn't even need to be said, but I've been an explicit dude for a long time! 😆)

I am still relatively new to the functional programming world, but one of the main fundamentals I've read quite a bit is that you don't want to throw an exception (unless it's actually exceptional). So this is what my default error handling code looks like:

case error do
  {:error, reason} when is_bitstring(reason) -> {:error, reason}
  {:error, reason} -> {:error, inspect reason}
  err -> {:error, inspect err}
end

Basically, I want to get it into the format of {:error, reason} as quickly as possible in the call stack, and I always want those reasons to be strings. So if I happen to have someone else's code in the with statement, and they don't follow this pattern for whatever reason, then I want to coerce into this shape ASAP and as "gracefully" as possible without an exception. Basically with the last clause, I'm just saying "Shove this misshapen error into one that I like and don't throw an exception."

It's just this last case that I'm talking about handling. Would you be able to do this coercion yourself in the block do you think? (Because I think it should be apparent that I agree with you on the {:error, reason} shaped tuples!)

@CrowdHailer
Copy link
Owner

yest quite right on :reason1 matching on a return value of {:error, :reason1}

That is an interesting question. I don't normally wrap in the do block but only use functions that follow the pattern perfectly. I don't want to pollute OK.with at all for two reasons

  1. it could be a slippery slope of adding one more unusual case because lots of people do it
  2. Actually act as an force to encourage people to be strict in the libraries they write.

My solution is to use wrapped functions in the block

OK.with do
  a <- fetch_from_map(map, "a")
  b <- fetch_from_map(map, "b")
  # something with a & b
end

def fetch_from_map(map, key)
  case Map.fetch(map, key) do
    {:ok, v} ->
      {:ok, v}
     :error ->
      {:error, :not_found_in_map}
  end
end

However if I make any mistakes then it does blow up with not very helpful messages so handling the case you are talking about might be good.

OK.with do
  :just_an_atom
else
  %OK.UnexpectedReturnValue{value: garbage} ->
    IO.inspect(garbage)
end

@ghost
Copy link
Author

ghost commented Feb 20, 2017

Yes, I think we're pretty much in agreement on all points. I do think it's useful though to have the catchall garbage handler, and if you're able to code that into the macro itself so that I don't need to see that kind of noise - I think that would just be one more noise-reducing feature when using OK.with.

@CrowdHailer
Copy link
Owner

Just opened PR #11 to implement else

@CrowdHailer
Copy link
Owner

closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant