在研究
MaybeT
的 Haskell 实现时,我偶然发现了一个有趣的问题。某些函数执行产生的结果与我的预期不同。
我不确定这是一个错误还是完全是别的什么。
问题描述如下:
liftM
(definition)是fmap
类型下的Monad
函数。
它的类型签名是:
liftM :: (a -> b) -> m a -> m b
Just
是 Maybe 类型的构造函数。
Just 1
结果为 Just(1)
。
Just Nothing
结果为 Just(Nothing)
。
但是,当我使用
liftM Just
(相当于liftM $ Just
)时,它们的类型签名是相同的:
liftM Just :: Monad m => m a1 -> m (Maybe a1)
现在,当我执行以下代码行时:
(liftM Just) $ Just(1)
结果是:
Just(Just 1) :: Maybe (Maybe t) -- this aligns with my expectations.
但是当我执行这行代码时:
(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 ??
问题是 返回的类型与函数的类型签名不匹配。 (
liftM Just
)
我期望的结果是:
Just(Nothing) :: Maybe (Maybe t)
。然而,在GHCi
中,实际结果是Nothing :: Maybe t
——这就是我得到的。
这两种类型是不同的。
Just(Nothing) :: Maybe (Maybe a)
Nothing :: Maybe a
Left(Just 1) :: Either (Maybe a) (Maybe a)
Left 1 :: Either
我觉得
Just(Nothing)
和Nothing
代表不同的价值观,对吧?
它们不应该被混为一谈,不是吗?
那么,
liftM Just $ Nothing
返回Just(Nothing) :: Maybe (Maybe t)
不是更合适吗?
如果我想获得
Just(Nothing)
结果,我该怎么办?
这是 Haskell 的一个错误,还是这种差异背后还有其他原因?
感谢您的帮助!
首先:这与
liftM
没有任何关系。对于任何行为良好的 monad(包括 Maybe
),这确实相当于 fmap
,你应该始终使用它。
让我们引入一个与
Maybe
等效但名称不同的类型,这将使发生的事情更清楚。
data Maybe' a = Nothing' | Just' a
deriving (Show, Functor) -- requires {-# LANGUAGE DeriveFunctor #-} on top of your source file
现在,你先写的相当于
> fmap Just $ Just' 1
Just' (Just 1) :: Maybe' (Maybe Integer)
首先要强调的是,
Maybe
和Maybe'
单子之间没有相互作用。 (事实上,我们甚至还没有将 Maybe'
创建为 monad!)您只是将某个函数映射到 Maybe'
函子上,而该函数恰好具有类型 Integer -> Maybe Integer
。
在同一个框架下,你的第二次尝试是
> fmap Just Nothing'
Nothing' :: Maybe' (Maybe Integer)
在这种情况下,您是否通过
Just
并不重要,因为在 Nothing'
上映射任何内容总是会返回 Nothing'
。这与可能不那么令人困惑的情况相同
> fmap (+1) Nothing'
Nothing' :: Maybe' (Maybe Integer)
对于
Either
示例,它基本上是相同的故事,除了 Left
构造函数包含 Nothing'
没有的附加值,但这不参与任何 Functor
语义。