作为类型类实例的函数?

问题描述:

{-# LANGUAGE LambdaCase #-}作为类型类实例的函数?

我有一堆以各种方式编码失败的函数。例如:

  • f :: A -> Bool回报False失败
  • g :: B -> Maybe B'回报Nothing失败
  • h :: C -> Either Error C'回报Left ...失败

我想链以同样的方式,这些操作为Maybe单子,所以链接函数需要知道每个函数是否失败,然后才能继续下一个函数。为此,我写了这个类:

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'

然而,链接功能的用户希望能够结合fghf'g'h'并让他们只是工作。他不会知道函数的返回类型需要被转换,除非他查看了他所组合的每个函数的语义,并检查它们是否与范围内的任何其他实例匹配。对于普通用户来说,这是单调乏味而且太微妙的,尤其是对于绕过用户不得不选择正确实例的类型推断而言。

这些函数不是在知道如何使用它们的情况下创建的。所以我可以创建一个类型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类型类的实例为每个功能,即,(使用提供给上述类型类实例的名称),fagbhc,并f'a'g'b'h'c'为“无效”的功能,其中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 

虽然不一定要通过类特性来完成。也许有一些方法可以做到这一点,而不是类型类?

+1

如果你想链接他们像一个monad(与'do'符号),那么你会需要将它们全部转换为单个类型,然后您可以创建一个Monad实例。您声明您希望类型系统只是在没有提示或上下文的情况下通过失败找出函数意味着什么。理论上,我可以有无数个函数,每个函数都返回一个不同的Integer表示失败,编译器应该如何知道特定的Integer何时失败?它仅作为上下文具有价值。你不能指望编译器为你编写程序,否则我们都会使用Agda。 – bheklilr 2015-02-23 22:22:12

+0

是的,我不一定要把它变成一个monad,但它会是类似的。我不希望编译器知道哪些整数是失败的,我想以某种方式指出哪些整数是'f'的失败,并为'f2'指示一个不同的集合,依此类推。例如,在Python中,我可以将一个全局变量映射函数作为类型实例的一些等价物。那么如果没有用户想要使用的某个函数的映射,它会在运行时崩溃而不是编译时。但它会更安全。 – 2015-02-23 22:26:00

不要这样做。 Haskell的静态类型系统和参考透明性为您提供了非常有用的保证:您可以确定某些特定的值意味着相同的事情,而不管它是如何生成的。对于这种干扰既没有可变性,也没有动态风格的表达式的运行时重新解释”,正如你所期望的那样。

如果你那里的那些功能没有相应地遵守这样的规范,那么这很糟糕。更好地摆脱它们(至少,隐藏他们,并只导出重新定义的版本与统一的行为)。或者告诉用户他们将不得不一起查找每个用户的规格。但不要试图绕开这个破碎定义的特定症状。

你可以适用于只是“标志”功能的一个简单变化,其中失败意味着它,否则确实是让他们回归这样的包裹的结果正好相反:

newtype Anti a = Anti { profail :: a } 

instance (Anti a) => Fail (Anti a) where 
    isFail (Anti a) = not $ isFail a 

注意:“同样的事情”可能非常抽象。有没有必要为Left是普遍一个“失败构造”,这是足够了很明显,这是相关联的第一个类型参数的变型的构造,这是什么仿/单子实例从它 –工作自动跟随,这将“意味着”在monadic应用程序中失败。
即,当你选择了正确的类型时,东西应该是非常自动的;当你仅仅是tossing around booleans,显然是相反的,所以也许你应该完全摆脱那些...

+0

我将不得不重新阅读你的答案,但是,输出布尔值在我的实际用例中很有用,因为我不是使用函数来实现输出是否成功的一元操作。 – 2015-02-24 00:08:17

+1

请注意,base 4.8为'Data.Monoid'添加了一个'Alt'类型(将'Alternative'变成'Monoid'),所以这可能不是最好的选择名称。 – dfeuer 2015-02-24 00:27:42

+0

@og_loc:monadic操作不应该输出布尔值来表示失败,只不过是普通函数。对于后者,您使用合适的'Maybe'或'Either'包装;前者应该用相应的monad变压器来完成。 – leftaroundabout 2015-02-24 14:03:14