我已经查看了MonadTrans的一些实例,对于MaybeT,实现看起来像这样:
instance MonadTrans MaybeT where
lift = MaybeT . liftM Just
据我所知,MonadIO的实例用于从最里面进行可变数量的提升,一个IO monad,直接到最外层。对于MaybeT案例,它看起来像这样:
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
我不明白这个递归函数是如何逃避无限循环的。什么是基本情况?
答案 0 :(得分:5)
也许令人惊讶的是,下面的定义是不递归,即使看起来如此。
instance (MonadIO m) => MonadIO (MaybeT m) where
liftIO = lift . liftIO
这是因为左侧的liftIO
是liftIO
monad的MaybeT m
,而右侧的liftIO
是{{1} } liftIO
monad。
因此,这只是根据另一个monad的m
在一个monad中定义liftIO
。这里没有递归。
这类似于例如。
liftIO
上面,我们定义了如何打印一对,具体取决于如何打印它们的组件。它看起来像递归,但事实并非如此。
它可以通过插入显式类型参数来帮助可视化,至少在心理上:
instance (Show a, Show b) => Show (a,b) where
show (x,y) = "(" ++ show x ++ ", " ++ show y ++ ")"
现在-- pseudo-code
instance (Show a, Show b) => Show (a,b) where
show @(a,b) (x,y) =
"(" ++ show @a x ++ ", " ++ show @b y ++ ")"
,show @(a,b)
和show @a
是不同的功能。
答案 1 :(得分:4)
一些专业化的简单等式推理和重写定义可以帮助您。 MonadIO
的基本情况为IO
。 MaybeT
是monad转换器,因此我们将MaybeT
和IO
合并为一个简单示例。
foo :: MaybeT IO String
foo = liftIO getLine
现在让我们一步一步地从你的问题中重写这个函数定义。
foo
= liftIO {- for MaybeT -} getLine
= lift (liftIO {- for IO here -} getLine) -- step 2
= lift (id getLine)
= lift getLine
= MaybeT (liftM Just getLine)
getLine
的类型为IO String
liftM Just getLine
的类型为IO (Maybe String)
MaybeT m a
构造函数需要m (Maybe a)
类型的值,其中m = IO
和a = String
在我们的案例中。分析可能最困难的步骤是第2步。但实际上,如果你提醒自己liftIO :: IO a -> m a
和lift :: Monad m => m a -> t m a
的类型,这很容易。因此,所有工作都是通过类型推断来完成的。