{-# 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'
和{{ 1}}并让他们工作。他不会知道函数的返回类型需要转换,除非他查看他正在组合的每个函数的语义,并检查它们是否与范围内的h'
个实例匹配。对于普通用户来说,这是一件单调乏味且过于微妙的事情,特别是在类型推断中绕过用户必须选择正确的实例。
这些功能并非在了解如何使用这些功能的情况下创建。所以我可以创建一个类型Fail
并围绕每个函数创建包装器。例如:
data Result a b = Fail a | Success b
fR = (\case True -> Sucess (); False -> Fail ()) . f
f'R = (\case False -> Sucess (); True -> Fail ()) . f'
gR = (\case Just a -> Sucess a; Nothing -> Fail ()) . g
g'R = (\case Nothing -> Sucess (); Just e -> Fail e) . g'
hR = (\case Left e -> Fail e; Right a -> Sucess a) . h
然而,这感觉很脏。我们正在做的只是证明/解释每个h'R = (\case Right e -> Fail e; Left a -> Sucess a) . h'
,f
,g
,h
,f'
和g'
是如何进行的在组合函数的上下文中使用。有更直接的方法吗?我想要的确切方法是说明每个函数应使用h'
类型类的哪个实例,即(使用上面提到的类型类实例的名称),Fail
→{{1} },f
→a
,g
→b
,h
→c
,f'
→a'
,g'
→b'
用于“无效”功能,其中h'
,c'
和a'
被定义为以下实例(与之前的实例重叠) ,所以你需要能够以某种方式通过名字选择它们:
b'
但它不一定要通过类型类来完成。除了类型类别之外,还有其他方法可以做到这一点吗?
答案 0 :(得分:10)
不要这样做。 Haskell的静态类型系统和引用透明性为您提供了非常有用的保证:您可以正确地确定某个特定的值意味着同样的事情 1 ,无论它是如何生成的。既不是可以干扰这一点,也不是动态风格的“运行时重新解释”表达式,因为你需要你想象的任务。
如果那里的那些功能没有相应地遵守这样的规范,那么这很糟糕。更好地摆脱它们(至少,隐藏它们并且只导出具有统一行为的重新定义的版本)。或者告诉用户他们必须要查看每个用户的规格。但是,不要试图破解这种破坏定义的特定症状。
一个简单的改变你可以应用于仅仅“标记”失败意味着相反的函数,否则它们会让它们返回这样的包装结果:
newtype Anti a = Anti { profail :: a }
instance (Anti a) => Fail (Anti a) where
isFail (Anti a) = not $ isFail a
1 心灵:可能非常抽象的“同样的东西”。 Left
无需普遍成为“失败的构造函数”,显然它是与第一个类型参数关联的变体构造函数就足够了,不是什么functor / monad实例在上运行 - 从而自动跟随这将在monadic应用程序中“意味着”失败。
即,当你选择了正确的类型时,东西应该是非常自动的;当你只是tossing around booleans时,显然恰恰相反,所以也许你应该完全摆脱那些......