这或多或少是我试图在自己的代码中理解的问题的简化版本。我正在使用来自 this package 的 Failure
类中的多态函数。 .
{-# LANGUAGE FlexibleContexts #-}
import Data.Maybe
import Data.Either
import Control.Failure
data FookError = FookError deriving Show
fook :: (Failure FookError m)=> Int -> m Int
fook = undefined
fooks :: (Failure FookError m)=> Int -> m Int
-- DOES NOT TYPE-CHECK:
--fooks n = return $ head $ rights $ map fook [1..]
-- OKAY:
fooks n = return $ head $ catMaybes $ map fook [1..]
您可以在上面的代码中看到,当我将 fook
的返回类型视为 Maybe Int
时,模块可以正常编译,但是将其视为 Either Fook Int
失败。
这是怎么回事?
最佳答案
这是因为,在 fooks
的非工作定义中,fook
的类型不明确。当您使用 catMaybes
时,它会消除歧义为 Maybe Int
,但当您使用 rights
时,它可以是 Either e Int
> 对于 any e
,编译器不一定知道是哪一个。当然,默认情况下,Either
的唯一 Failure
实例是 instance Failure e (Either e)
,但是没有什么可以阻止您定义,例如实例失败字符串(整数)
。
如果您通过将 fooks
定义为显式指定类型
fooks n =
return $ head $ rights $ map (fook :: Int -> Either FookError Int) [1..]
然后就可以正常工作了。
但是,我怀疑您在这里并没有做您真正想做的事; fooks
实际上从未使用底层 monad 的失败功能;事实上,即使没有非失败结果,单子(monad)操作仍然会成功并返回一个值。该值恰好是一个错误,但这可能仍然不是您想要的:)
如果您希望 fooks
依次尝试一堆单独的 fook
,并返回第一个成功的,则类似于:
fooks :: (Failure FookError m, MonadPlus m) => Int -> m Int
fooks n = foldr mplus (failure FookError) $ map fook [1..]
应该可以解决问题。普通的 Failure
类本身无法提供从错误中恢复的方法,因此您还需要需要 MonadPlus
。请注意,这里的 failure FookError
永远不会被实际使用,因为 [1..]
是无限的,但大概您打算更改定义;对实际使用 n
的人说:)
不幸的是,这还不是全部! Either e
没有 MonadPlus
实例,大概是因为 mzero
没有合理的值(尽管另一个潜在的问题是 mplus (Left e) (Left e')
可以是 Left e
或 Left e'
)。
幸运的是,为我们的特定类型定义一个实例很容易:
instance MonadPlus (Either FookError) where
mzero = failure FookError
mplus a@(Right _) _ = a
mplus (Left _) a = a
您需要在文件顶部添加 {-# LANGUAGE FlexibleInstances #-}
才能执行此操作。
关于haskell - 'Either e` 包中 `Failure` 的 "failure"实例存在问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8920133/