monadic
monadic copied to clipboard
Cannot specify nil as the desired fetch value from Monadic:Nothing
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
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
@paploo have you ever found a sound way to get around this?
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