While studying the Haskell implementation of MaybeT, I stumbled upon an intriguing issue. Some function executions yield results that differ from my expectations.
I'm unsure whether this is a bug or something else entirely.
Here's a description of the problem:
liftM(definition) is the fmap function under the Monad type.
Its type signature is:
liftM :: (a -> b) -> m a -> m b
Just is a constructor for the Maybe type.
Just 1 results in Just(1).
Just Nothing results in Just(Nothing).
However, when I use liftM Just (equivalent to liftM $ Just), their type signatures are the same:
liftM Just :: Monad m => m a1 -> m (Maybe a1)
Now, when I execute the following line of code:
(liftM Just) $ Just(1)
The result is:
Just(Just 1) :: Maybe (Maybe t) -- this aligns with my expectations.
But when I execute this line of code:
(liftM Just) $ Nothing -- I expected: Just(Nothing) :: Maybe(Maybe a),
-- but actual result: Nothing :: Maybe a ??
(liftM Just) $ (Left 1) -- I expected: Left(Just 1), but actual result: Left 1 ??
The problem is the returned type doesn't match the function's type signature. (liftM Just)
I expected the result to be: Just(Nothing) :: Maybe (Maybe t). However, in GHCi, the actual result is Nothing :: Maybe t — this is what I got.
The two types are different.
Just(Nothing) :: Maybe (Maybe a)
Nothing :: Maybe a
Left(Just 1) :: Either (Maybe a) (Maybe a)
Left 1 :: Either
I feel that Just(Nothing) and Nothing represent distinct values, right?
They shouldn't be conflated, should they?
So, Wouldn't it be more appropriate for liftM Just $ Nothing to return Just(Nothing) :: Maybe (Maybe t)?
If I want to get a Just(Nothing) as a result, What Can I do?
Is this a bug for Haskell, or is there some other reason behind this discrepancy?
Thanks for your help!
First: this does not have anything to do with
liftMin particular. For any well-behaving monad (which includesMaybe) this is indeed equivalent tofmap, which is what you should always use instead.Let's introduce a type equivalent to
Maybebut differently named, this will make it clearer what's going on.Now, what you wrote first is equivalent to
First to emphasize that there is no interaction between the
MaybeandMaybe'monads. (In fact we haven't even madeMaybe'a monad!) You're simply mapping some function over theMaybe'functor, and that function happens to have typeInteger -> Maybe Integer.In the same framework, your second attempt is
In this case it doesn't really matter at all that you pass
Just, because mapping anything overNothing'always gives you backNothing'. It's the same as with the perhaps less confusingFor the
Eitherexample it's basically the same story, except that theLeftconstructor contains an additional value thatNothing'does not have, but this does not partake in anyFunctorsemantics.