【问题标题】:haskell, how to deal with mismatched types - monadshaskell,如何处理不匹配的类型 - monads
【发布时间】:2016-04-20 19:45:46
【问题描述】:

我无法处理以下问题:
我有一些函数,返回 foo :: a -> b -> ErrorT String IO Int
我知道它返回IO (Either String Int)
不过,我还有更复杂的函数,返回:

bar :: a -> b -> StateT Char (ReaderT Char (ErrorT String IO)) Int

此函数bar 调用函数foo。我将要做以下事情:
如果 foo 调用 throwError bar 也会抛出相同的错误。如果foo 返回Int bar 也返回Int

但是,这可能是由于类型不匹配。

我不知道如何以优雅的方式做到这一点。 我认为我的转换器单子顺序不正确。

【问题讨论】:

  • 能否请您在bar调用 foo - 您可能只需要正确的lift 吗?
  • 是的,非常感谢。我使用了lift $ lift $ ,编译器接受了它。我不知道 - 我以为电梯会自动完成。
  • 这会让使用 monad-stacks 变得更容易 ;) - 但没关系 - 也许你想写下你自己的答案或在问题为你解决后关闭它
  • 不使用具体类型,使用MonadStateMonadReaderMonadError 可能更有意义——这样您就不必使用lift

标签: haskell monads


【解决方案1】:

这样的事情应该可以工作:

bar x y = do
  -- some code
  z <- lift $ lift $ foo x y
  -- some more code
  return z

【讨论】:

    【解决方案2】:

    我通常做的是使用我称为 Transformer Monad Classes 的东西

    {-# LANGUAGE FlexibleContexts #-}
    
    import Control.Monad.Reader
    import Control.Monad.State
    
    inner :: ReaderT Char IO Int
    inner = do
        a <- ask
        lift $ print a
        return 5
    
    inner' :: (MonadReader Char m, MonadIO m) => m Int
    inner' = do
        a <- ask
        liftIO $ print a
        return 5
    
    outer :: StateT Char (ReaderT Char IO) Int
    outer = do
        a <- get
    
        b <- lift $ inner    -- need to lift
        c <- inner'          -- no need to lift
    
        lift . lift $ print "need to lift twice to get to IO"            
    
        return 5
    
    main = runReaderT (runStateT outer 'b') 'a'
    

    让我们分解一下;第一个 inner 函数有一个具体类型,您需要直接提升到才能使用它。但是,如果您对其进行参数化,仅在签名中留下 capabilities,则可以跳过提升,只要它在堆栈中获得此功能的位置是明确的。在这种情况下很清楚,因为StateT Char (ReaderT Char IO) Int 恰好有一个MonadIO 实例(来自IO)和一个MonadReader Char 实例(来自ReaderT ... Char)。

    现在,只要实例清晰,您需要进行多少次提升并不重要!考虑:

    outer' :: ErrorT String (StateT Char (ReaderT Char IO)) Int
    outer' = do
        a <- inner'               -- still no need to lift!
        b <- lift . lift $ inner  -- need to double lift in this case
        return 5
    

    最后一点可能不是很明显,outer 的签名也可以用通用方式表示:

    outer :: (MonadState Char m, MonadReader Char m, MonadIO m) => m Int
    

    并且它仍然可以在没有任何提升的情况下工作(对于 IO 操作,没有 liftIO,因为像 print 这样的函数是根据 IO 定义的,而不是 MonadIO m。相比之下,像 askget 是根据各自的 MonadX 类定义的,它允许我们跳过提升)。

    【讨论】:

      猜你喜欢
      • 2014-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-04
      • 2018-03-16
      • 2017-08-28
      • 1970-01-01
      相关资源
      最近更新 更多