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

Consider an explicit Result monad #42

Closed
sporto opened this issue Aug 3, 2017 · 5 comments
Closed

Consider an explicit Result monad #42

sporto opened this issue Aug 3, 2017 · 5 comments

Comments

@sporto
Copy link

sporto commented Aug 3, 2017

The Either monads doubles as Result in dry-rb. However I found the way .value works to be unexpected.

Consider this:

result = format_time("Invalid input")

"The time is #{result.value}"

This snippet would produce a wrong output e.g. "The time is Wrong Input".

Optimally I would do .success? before doing this. But ruby is not haskell and pattern matching is not required. So is easy to be lazy and code the happy path.

So maybe consider adding a Result monad that:

  • Raise if accessing value on failed result

result.value will raise error

  • Explicitly access the error using .error

e.g. result.error == "Wrong Input"

With something like this my above snippet will raise an error instead of giving me a wrong output, which I think is preferable.

@timriley
Copy link
Member

timriley commented Aug 4, 2017

Hi @sporto, thanks for the ideas here! However, what you're proposing isn't technically a monad anymore, so I'm not sure if it fits in this library, which is designed to offer monad functionality made as nice as possible for Ruby.

It's true that Ruby is not Haskell, but I reckon working with monads in Ruby can still feels pretty natural once you've learnt the small API they offer. And in doing this with Either, you get the benefit of being encouraged to think about error-handling as an equal matter, rather than something exceptional.

You don't always need to check for #success?, either. A more declarative approach might be:

result = format_time("invalid input")

time_str = result.fmap { |time|
  "The time is #{time}"
}.or_fmap { |err|
  "Error determining time: #{err}
}.value

And if you think that looks too bulky, it's the perfect thing to hide behind a well-named method ;)

If you want exceptions to be raised, perhaps a one-liner like this (accessing #right directly)?

time_str = result.right or raise "error calculating time"

Of course, I'm sure you've considered all of these things :) So even if your proposed "explicit result" object isn't something we incorporate in dry-monads, it'd be pretty easy for you to write as a wrapper:

class ExplicitResult < SimpleDelegator
  FailureError = Class.new(StandardError)

  def value
    if success?
      super
    else
      raise FailureError
    end
  end
end

Something like this you could pop into your app or even a gem on its own?

@flash-gordon any thoughts to add on this?

@flash-gordon
Copy link
Member

@timriley I thought about this, like, yesterday. The reason we have value behaving like this is compatibility with the kleisli gem, I was too shy to change it :) Now I think this method must go for right-biased monads and has to be replaced with value! that raises an error once called on a left-side structure (None/Left/etc). I'd expected it to work like this

user = create_user.(data).value!
# returns a user object or raises an error with the provided message
user = create_user.(data).value!("No user") 
# can use blocks for messages
user = create_user.(data).value! { "No user" }

Left values can be unwrapped with

failure.flip.value!

@sporto you should use value_or for cases where you don't want to raise an error (just in case you missed it)

@timriley
Copy link
Member

timriley commented Aug 4, 2017

@flash-gordon I really like the idea of value! It matches the semantics (and aesthetics, even) of "force unwrapping" optionals in languages like swift, too. I'd be in favor of this going in!

And on the plus side, I think this would give @sporto the behavior he wants, too?

@flash-gordon
Copy link
Member

flash-gordon commented Aug 4, 2017

@timriley as a side note, there is a simple reason why this semantics is aligned with other statically typed languages. Result (or Either as an implementation) consists of two types, so called "success type" and "failure type". Of course they are different most of the time, otherwise, this doesn't make much sense. Thus, you simply cannot have a method/function that returns different types depending on some condition, functions don't work this way in static languages, they return you something of a certain type or raise an exception.

@sporto
Copy link
Author

sporto commented Aug 4, 2017

Thanks
.value! as described above is pretty much what I would like to use.
I also missed that there is a .right method. That doesn't seem to be in the doc.

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

3 participants