我正在尝试在我正在使用的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 { ... } } }
我正在编辑我的问题以及评论,因为好的代码格式在这里比在评论中更容易。
答案 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 IO
和runStateT
,您可以强制或约束类型,以便最终满足类约束。如果你的问题稍有不妥,这有点令人困惑。
哦,关于编写解决方案的惯用方法:我不确定单独的runEitherT
函数在这里是不是惯用的,因为它看起来像是在干涉全局状态,即打开某种数据库文件。这取决于图书馆的成语是什么样的。
此外,通过使用retEither
,您在评估后隐含地丢弃了状态,这可能是也可能不是一个坏主意。库是否希望您重用数据库连接?
最后,你有一些额外的括号和一些丢失的类型签名; hlint
会帮助您解决这些问题。