“失败”包中“失败”的'e'实例的困难

时间:2012-01-19 01:55:07

标签: haskell types polymorphism

这或多或少是我在自己的代码中试图理解的问题的简化版本。我正在使用来自this packageFailure类中具有多态性的函数。

{-# 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会失败。

这里发生了什么?

1 个答案:

答案 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 eLeft e')。

值得庆幸的是,为我们的特定类型定义一个实例很容易:

instance MonadPlus (Either FookError) where
  mzero = failure FookError
  mplus a@(Right _) _ = a
  mplus (Left _) a = a

您需要在文件顶部{-# LANGUAGE FlexibleInstances #-}才能执行此操作。