递归liftIO

时间:2017-03-21 11:08:45

标签: haskell monads monad-transformers

我已经查看了MonadTrans的一些实例,对于MaybeT,实现看起来像这样:

instance MonadTrans MaybeT where
    lift = MaybeT . liftM Just

据我所知,MonadIO的实例用于从最里面进行可变数量的提升,一个IO monad,直接到最外层。对于MaybeT案例,它看起来像这样:

instance (MonadIO m) => MonadIO (MaybeT m) where
    liftIO = lift . liftIO

我不明白这个递归函数是如何逃避无限循环的。什么是基本情况?

2 个答案:

答案 0 :(得分:5)

也许令人惊讶的是,下面的定义是递归,即使看起来如此。

instance (MonadIO m) => MonadIO (MaybeT m) where
    liftIO = lift . liftIO

这是因为左侧的liftIOliftIO 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的基本情况为IOMaybeT是monad转换器,因此我们将MaybeTIO合并为一个简单示例。

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)
  1. getLine的类型为IO String
  2. liftM Just getLine的类型为IO (Maybe String)
  3. MaybeT m a构造函数需要m (Maybe a)类型的值,其中m = IOa = String在我们的案例中。
  4. 分析可能最困难的步骤是第2步。但实际上,如果你提醒自己liftIO :: IO a -> m alift :: Monad m => m a -> t m a的类型,这很容易。因此,所有工作都是通过类型推断来完成的。