函数作为类型类的实例?

{-# 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在失败时返回Just Error (成功时Nothing
  • h' :: C -> Either C' Error在失败时返回Right ...
  • 这些可以通过简单地使用转换它们的函数来包装它们来解决,例如:

  • f'' = not . f' 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'并让它们正常工作。 他不会知道函数的返回类型需要被转换,除非他查看他正在合并的每个函数的语义,并检查它们是否与范围内的任何Fail实例相匹配。 对于普通用户来说,这是单调乏味而且太微妙的,尤其是对于绕过用户不得不选择正确实例的类型推断而言。

    这些功能不是在知道如何使用它们的情况下创建的。 所以我可以创建一个类型data Result ab = Fail a | Success b data Result ab = 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'
  • 但是,这感觉很脏。 我们所做的只是验证/解释在组合函数的上下文中如何使用fghf'g'h'中的每一个。 是否有更直接的方式来做到这一点? 我想要的是一种方式来说明哪个实例的Fail类型应该用于每个函数,即(使用上面给出的类型类实例的名称), fagbhcf'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
    

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


    不要这样做。 Haskell的静态类型系统和参照透明性为您提供了极大的有用保证:您可以确定某个特定的值意味着相同的事物1,而不管它是如何生成的。 对于这种干扰既没有可变性,也没有对表达式的动态样式“运行时重新解释”,因为您需要完成您所期望的任务。

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

    一个简单的变化,你可以申请只是“标志”失败意味着相反的功能,否则会让他们返回这样一个包装的结果:

    newtype Anti a = Anti { profail :: a }
    
    instance (Anti a) => Fail (Anti a) where
      isFail (Anti a) = not $ isFail a
    

    1Mind:可能非常抽象意义上的“同一件事”。 没有必要让Left成为普遍的“失败构造函数”,显然它是与第一个类型参数关联的变体构造函数就足够了,这不是函数/ monad实例的操作 - 从它自动遵循这个将“意味着”在一次性应用程序中失败。
    也就是说,当你选择了正确的类型时,东西应该是非常自动的; 显然,当你只是围绕布尔变换时,情况正好相反,所以也许你应该完全摆脱那些......

    链接地址: http://www.djcxy.com/p/7541.html

    上一篇: Functions as instances of typeclasses?

    下一篇: How is the instance of Show for String written?