{-# LANGUAGE LambdaCase #-}
我有一堆函数以各种方式编码失败.例如:
f :: A -> BoolFalse失败时返回g :: B -> Maybe B'Nothing失败时返回h :: C -> Either Error C'Left ...失败时返回我想以与Maybemonad 相同的方式链接这些操作,因此链接函数需要知道每个函数在继续下一个函数之前是否失败.为此,我写了这堂课:
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
Run Code Online (Sandbox Code Playgroud)
但是,可能存在不符合的函数:
f' :: A -> BoolTrue失败时返回g' :: B -> Maybe ErrorJust Error失败时返回(Nothing成功时)h' :: C -> Either C' ErrorRight ...失败时返回这些可以通过简单地用变换它们的函数包装它们来解决,例如:
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'用于"无效的"函数,where a',b'和,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
Run Code Online (Sandbox Code Playgroud)
但它不一定要通过类型类来完成.除了使用类型类别外,还有什么方法可以做到这一点?
lef*_*out 10
不要这样做.Haskell的静态类型系统和引用透明性为您提供了非常有用的保证:您可以正确地确定某个特定值意味着相同的东西1,无论它是如何生成的.既不是可以干扰这一点,也不是动态风格的"运行时重新解释"表达式,因为你需要你想象的任务.
如果那里的那些功能没有相应地遵守这样的规范,那么这很糟糕.更好地摆脱它们(至少,隐藏它们,只导出具有统一行为的重新定义的版本).或者告诉用户他们必须要查看每个用户的规格.但是,不要试图破解定义破坏的特定症状.
一个简单的改变你可以应用于只是"标记"失败意味着相反的函数,否则它们会让它们返回这样一个包装结果:
newtype Anti a = Anti { profail :: a }
instance (Anti a) => Fail (Anti a) where
isFail (Anti a) = not $ isFail a
Run Code Online (Sandbox Code Playgroud)
1 心灵:可能非常抽象的"同样的东西".没有必要Left普遍成为"失败的构造函数",它很明显它是与第一个类型参数关联的变体构造函数,而不是函数/ monad实例操作的 - 从它自动跟随它将在monadic应用程序中"意味着"失败.
也就是说,当你选择了正确的类型时,东西应该是非常自动的; 很明显,当你只是在布尔人周围折腾时,情况恰恰相反,所以也许你应该彻底摆脱那些......