在haskell程序中使用返回的EitherT

时间:2014-09-29 23:50:36

标签: haskell monads monad-transformers either io-monad

我正在尝试在我正在使用的Haskell项目中使用“citation-resolve”包,但是我无法在实际代码中使用EitherT。我知道他们是monad变形金刚,我想我明白这意味着什么,但我似乎无法真正弄清楚如何使用它们。代表我正在尝试做的事情的玩具示例如下:

module Main where
import Text.EditDistance
import Text.CSL.Input.Identifier
import Text.CSL.Reference
import Control.Monad.Trans.Class 
import Control.Monad.Trans.Either 

main = do
    putStrLn "Resolving definition"
    let resRef = runEitherT $ resolveEither "doi:10.1145/2500365.2500595"
    case resRef of 
                Left e -> do 
                    putStrLn ("Got error: "++ e)
                Right ref -> do
                    putStrLn ("Added reference to database: "++ (show ref))

此处,resolveEither的类型为:

resolveEither :: (HasDatabase s,
                  Control.Monad.IO.Class.MonadIO m,
                  mtl-2.1.3.1:Control.Monad.State.Class.MonadState s m)
                   => String -> EitherT String m Reference

runEitherT $ resolveEither "ref"的类型为:

runEitherT $ resolveEither "ref"
   :: (HasDatabase s,
       Control.Monad.IO.Class.MonadIO m,
       mtl-2.1.3.1:Control.Monad.State.Class.MonadState s m)
         => m (Either String Reference)

但是,这会出现以下错误:

Main.hs:10:34:
    No instance for (Control.Monad.IO.Class.MonadIO (Either [Char]))
      arising from a use of ‘resolveEither’
    In the first argument of ‘runEitherT’, namely
      ‘(resolveEither "doi:10.1145/2500365.2500595")’
    In the expression:
      runEitherT (resolveEither "doi:10.1145/2500365.2500595")
    In an equation for ‘resRef’:
        resRef = runEitherT (resolveEither "doi:10.1145/2500365.2500595")

我不知道如何解决或解决问题。

任何帮助都会受到赞赏,尤其是从使用角度来看处理monad变换器的教程的指针,而不是实现的。

编辑:

为了反映dfeuer和Christian对答案的评论,如果我将主要内容更改为以下内容,我仍会收到错误:

main = do
    putStrLn "Resolving definition"
    resRef <- runEitherT (resolveEither "doi:10.1145/2500365.2500595")
    case resRef of 
                Left e -> do 
                    putStrLn ("Got error: "++ e)
                Right ref -> do
                    putStrLn ("Added reference to database: "++ (show ref))

我现在得到的错误是:

No instance for (MonadState s0 IO)
  arising from a use of ‘resolveEither’
In the first argument of ‘runEitherT’, namely
  ‘(resolveEither "doi:10.1145/2500365.2500595")’
In a stmt of a 'do' block:
  resRef <- runEitherT (resolveEither "doi:10.1145/2500365.2500595")
In the expression:
  do { putStrLn "Resolving definition";
       resRef <- runEitherT (resolveEither "doi:10.1145/2500365.2500595");
       case resRef of {
         Left e -> do { ... }
         Right ref -> do { ... } } }

我正在编辑我的问题以及评论,因为好的代码格式在这里比在评论中更容易。

3 个答案:

答案 0 :(得分:7)

我认为问题是你在resRef上尝试模式匹配,当你可能想做的是执行它和模式匹配时结果。

所以你应该试试这个:

main = do
    putStrLn "Resolving definition"
    resRef <- runEitherT $ resolveEither "doi:10.1145/2500365.2500595"
    case resRef of 
                Left e -> do

答案 1 :(得分:1)

好的,所以我想我已经找到了原始问题的解决方案,它从函数IO (Either String Reference)获得了resolveEither类型的值(它为{{1}它提供的功能)。

因此,resolveDef返回一种

resolveEither

我们可以将其转换为

类型之一
(HasDatabase s, MonadIO m, MonadState s m) => String -> EitherT String m Reference 

使用(HasDatabase s, MonadIO m, MonadState s m) => String -> m (Either String Reference) 。当我问这个问题时,这就是我要去的地方。从那里开始,我尝试查看源代码,了解库如何从函数runEitherT . resolveEither中提取Reference类型。该库使用以下函数:

resolveEither

但是,我们希望保留其中之一,即删除resolve :: (MonadIO m, MonadState s m, HasDatabase s) => String -> m Reference resolve = liftM (either (const emptyReference) id) . runEitherT . resolveEither

然而,这让我们回到了我们开始的地方,所以我再次查看了源代码,并找出了如何使用这个函数。在库中,该函数在以下内容中使用,它将liftM (either (const emptyReference) id)的输出类型从类型resolve的值转换为类型(MonadIO m, MonadState s m, HasDatabase s) => m Reference的值:

IO Reference

我们可以使用resolveDef :: String -> IO Reference resolveDef url = do fn <- getDataFileName "default.db" let go = withDatabaseFile fn $ resolve url State.evalStateT go (def :: Database) 替换上一个中的resolve来获取返回runEitherT.resolveEither的函数:

IO (Either String Reference)

(我已将retEither s = do fn <- getDataFileName "default.db" let go = withDatabaseFile fn $ ( (runEitherT.resolveEither) s) State.evalStateT go (Database Map.empty) 替换为(def :: Database),因为def仅在(Database Map.empty)内部定义

然后整体解决方案变为:

citation-resolve

这解决了原来的问题!

关于风格的任何指示,或简化整个过程的方法都会非常受欢迎。

答案 2 :(得分:1)

您遇到了mtl基于类的方法的一个缺点:恐吓类型错误。我认为有必要想象一下基于普通transformers的monad变换器的情况会是什么样子。我希望这也可以帮助你掌握Monad变形金刚。 (顺便说一下,你似乎已经理解了大部分内容;我只是拼写出来。)

给出类型是一个很好的开始方式。这就是你所拥有的:

resolveEither :: (HasDatabase s,
                  MonadIO m,
                  MonadState s m)
                   => String -> EitherT String m Reference

隐藏在约束中的类型s,稍后会再次咬你。粗略地说,约束表达了以下内容:s有一个数据库(无论在上下文中意味着什么); monad或monad堆栈m在其基础上有IO,monad堆栈m中的某个地方是StateT s层。满足这些属性的最简单的monad堆栈m将是HasDatabase s => StateT s IO。所以我们可以这样写:

resolveEither' :: HasDatabase s
                  => String -> EitherT String (StateT s IO) Reference
resolveEither' = resolveEither

我们所做的只是指定m的类型,因此它不再是变量。只要我们满足类约束,我们就不需要

现在更清楚的是,有两层monad变换器。由于我们的主要功能位于IO monad中,因此我们希望最终得到IO类型的值,我们可以&#34;运行&#34;,例如使用<-do表示法。我认为它是&#34;剥离&#34; monad变换器的层,从out到in。(这就是&#34;使用&#34; monad变换器归结为。)

对于EitherT,有一个函数runEitherT :: EitherT e m a -> m (Either e a)。了解m如何从&#34;内部&#34; EitherT到&#34;外面&#34;?对我来说,这是关键的直观观察。同样地,对于StateT,还有runStateT :: StateT s m a -> s -> m (a, s)

(顺便说一句,两者都被定义为记录访问者,这是惯用的,但会导致他们在Haddock中出现一些奇怪的东西以及&#34;错误的&#34;类型签名;我花了一些时间学习看在Haddocks的&#34;构造函数&#34;部分中,在心理上将EitherT e m a ->等添加到签名的前面。)

因此,这就构成了一个通用的解决方案,您已经基本解决了这个问题:我们需要s类型的适当值(我会调用s),然后我们可以使用类型为flip runStateT s . runEitherT $ resolveEither "ref"的{​​{1}}。 (假设我把这些类型直接保留在我脑海中,我可能没有。我第一次忘记IO ((Either String Reference), s)。)然后我们可以模式匹配或使用flip到达fst,这似乎是你真正想要的。

如果您希望我明白GHC给您的错误,我会感到高兴。非正式地,它是说你没有&#34;跑步&#34;或剥离所有的monad变形金刚。更准确地说,它观察到Either不像IO。通过使用StateT s IOrunStateT,您可以强制或约束类型,以便最终满足类约束。如果你的问题稍有不妥,这有点令人困惑。

哦,关于编写解决方案的惯用方法:我不确定单独的runEitherT函数在这里是不是惯用的,因为它看起来像是在干涉全局状态,即打开某种数据库文件。这取决于图书馆的成语是什么样的。

此外,通过使用retEither,您在评估后隐含地丢弃了状态,这可能是也可能不是一个坏主意。库是否希望您重用数据库连接?

最后,你有一些额外的括号和一些丢失的类型签名; hlint会帮助您解决这些问题。