这或多或少是我在自己的代码中试图理解的问题的简化版本。我正在使用来自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
会失败。
这里发生了什么?
答案 0 :(得分:4)
这是因为,在fooks
的非工作定义中,fook
的类型不明确。当您使用catMaybes
时,它会消除歧义为Maybe Int
,但是当您使用rights
时,任何 Either e Int
都可以e
,编译器不一定知道哪个。当然,默认情况下,Failure
的唯一Either
实例是instance Failure e (Either e)
,但是没有什么可以阻止您定义,例如instance Failure String (Either Int)
。
如果通过将fooks
定义为
fooks n =
return $ head $ rights $ map (fook :: Int -> Either FookError Int) [1..]
然后它运作正常。
然而,我怀疑你没有做你真正想要的事情; fooks
从未实际使用底层monad的失败功能;实际上,即使没有失败的结果,monadic动作仍然成功并返回一个值。这个值恰好是一个错误,但这可能仍然不是你想要的:)
如果您希望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 #-}
才能执行此操作。