【发布时间】:2015-02-24 16:34:44
【问题描述】:
{-# LANGUAGE LambdaCase #-}
我有一堆以各种方式编码失败的函数。例如:
-
f :: A -> Bool失败时返回False -
g :: B -> Maybe B'失败时返回Nothing -
h :: C -> Either Error C'失败时返回Left ...
我想以与Maybe monad 相同的方式链接这些操作,因此链接函数需要在继续执行下一个函数之前知道每个函数是否失败。为此我写了这个类:
class Fail a where
isFail :: a -> Bool
instance Fail () where
isFail () = False
instance Fail Bool where -- a
isFail = not
instance Fail (Maybe a) where -- b
isFail = not . isJust
instance Fail (Either a b) where -- c
isFail (Left _) = True
isFail _ = False
但是,可能存在不符合要求的函数:
-
f' :: A -> Bool失败时返回True -
g' :: B -> Maybe Error在失败时返回Just Error(Nothing在成功时) -
h' :: C -> Either C' Error失败时返回Right ...
可以通过简单地用转换它们的函数包装它们来解决这些问题,例如:
-
f'' = not . f'。 g'' = (\case Nothing -> Right (); Just e -> Left e) . g'h'' = (\case Left c -> Right c; Right e -> Left e) . h'
但是,链接函数的用户希望能够组合 f、g、h、f'、g' 和 h' 并让它们正常工作。他不会知道需要转换函数的返回类型,除非他查看他正在组合的每个函数的语义,并检查它们是否与他在范围内的任何 Fail 实例匹配。这对于普通用户来说是乏味且过于微妙的,尤其是在类型推断绕过了用户必须选择正确的实例的情况下。
这些函数不是在知道如何使用它们的情况下创建的。所以我可以创建一个类型 data Result a b = Fail a | Success b 并围绕每个函数进行包装。例如:
fR = (\case True -> Sucess (); False -> Fail ()) . ff'R = (\case False -> Sucess (); True -> Fail ()) . f'gR = (\case Just a -> Sucess a; Nothing -> Fail ()) . gg'R = (\case Nothing -> Sucess (); Just e -> Fail e) . g'hR = (\case Left e -> Fail e; Right a -> Sucess a) . hh'R = (\case Right e -> Fail e; Left a -> Sucess a) . h'
但是,这感觉很脏。我们所做的只是证明/解释f、g、h、f'、g' 和h' 是如何在组合函数的上下文中使用的。有没有更直接的方法可以做到这一点?我真正想要的是一种方式来说明每个函数应该使用Fail 类型类的哪个实例,即(使用给上面类型类实例的名称),f → a,g → b, h → c, 和 f' → a', g' → b', h' → c' 用于“无效”函数,@3, @987654473, @987654473 @ 和 c' 定义为以下实例(与前面的实例重叠,因此您需要能够以某种方式按名称选择它们):
instance Fail Bool where -- a'
isFail = id
instance Fail (Maybe a) where -- b'
isFail = isJust
instance Fail (Either a b) where -- c'
isFail (Right _) = True
isFail _ = False
虽然它不一定必须通过类型类来完成。除了使用类型类之外,也许还有其他方法可以做到这一点?
【问题讨论】:
-
如果您想像在 monad 中一样将它们链接在一起(使用
do表示法),那么您需要将它们全部转换为一个类型,然后您可以为其创建一个 Monad 实例。您声明您希望类型系统仅在没有提示或上下文的情况下找出函数失败的含义。理论上,我可以有无数个函数,每个函数都返回一个表示失败的不同 Integer,编译器应该如何知道特定 Integer 何时失败?它只有一个值作为上下文。你不能指望编译器为你编写程序,否则我们都会使用 Agda。 -
是的,我不一定想让它成为一个单子,但它会是相似的。我不希望编译器知道哪些整数是失败的,我想以某种方式为
f指明哪些整数是失败的,并为f2指明不同的集合,等等。例如,在 Python 中,我可以将全局变量映射函数映射到一些等价的类型类实例。如果用户想要使用的某个函数没有映射,它将在运行时而不是编译时崩溃。但它仍然会更安全。