pzol / monadic

helps dealing with exceptional situations, it comes from the sphere of functional programming and bringing the goodies I have come to love in Scala to my ruby projects
MIT License
229 stars 20 forks source link

Cannot specify nil as the desired fetch value from Monadic:Nothing #9

Open paploo opened 10 years ago

paploo commented 10 years ago

Example: I have a person object that might have a spouse object, and I want their first_name. So, if all is good, I'd call person.spouse.first_name. Of course, if we have no spouse there would be a crash of no method first_name on nil, so I use Maybe:

first_name = Maybe(person).spouse.first_name.fetch(nil)

In this case, as per idiomatic Ruby, I want nil as the default first_name if there is none. However, the value of first_name is not nil, but rather Monadic::Nothing, which is true valued in if satements! In other words

# Fails to work correctly because first name is Monadic::Nothing, not nil as expected!
do_something(first_name) if first_name

The bug is at https://github.com/pzol/monadic/blob/master/lib/monadic/maybe.rb#L78, where you use a magic value for the argument to decide if you have one, and that magic value happens to be a common default for idiomatic Ruby.

My recommended fix would be to capture the args as follows, which gives exactly the same behavior as the current version, but honors any default value:

def fetch(*args)
  case args.length
  when 0
    self
  when 1
    args.first
  else
    raise ArgumentError, "wrong number of arguments (#{args.length} for 0..1)", caller
  end
end
paploo commented 10 years ago

On second thought, I'm not sure why you'd ever want fetch to return Nothing, since the point of fetch is to unwrap the value in the monad and return to Ruby land. Why not just implement as:

def fetch(default=nil)
  default
end
SkyWriter commented 8 years ago

@paploo have you ever found a sound way to get around this?

sdbondi commented 8 years ago

Hi,

Made a PR for this. Basically if you don't give an explicit argument it will return Nothing as before. If you explicitly say "give me nil (or whatever value)" it will for Nothing.

Maybe(nil)._ == Nothing
Maybe(nil)._(nil) == nil
Maybe(nil)._('') == ''

EDIT: Just read @paploo's implementation, had the same idea :)

EDIT2: @paploo the only problem I found with making fetch return the default is the Elvis Operator.

# if:
nil._? == nil
# then we cant do:
nil._?.a.b # will throw undefined method `a' for nil:NilClass
# so the result should stay as it currently is
nil._?.a.b == Nothing

There may be other reasons, but I'm not aware of them