【问题标题】:Functions as instances of typeclasses?作为类型类实例的函数?
【发布时间】: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 ()) . 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 类型类的哪个实例,即(使用给上面类型类实例的名称),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 中,我可以将全局变量映射函数映射到一些等价的类型类实例。如果用户想要使用的某个函数没有映射,它将在运行时而不是编译时崩溃。但它仍然会更安全。

标签: haskell typeclass


【解决方案1】:

不要这样做。 Haskell 的静态类型系统和引用透明性为您提供了非常有用的保证:您可以正确地确定某个特定的 值 表示相同的东西1,而不管它是如何产生的。既不存在干扰这一点的可变性,也不存在动态样式的表达式“运行时重新解释”,因为您似乎需要完成您设想的任务。

如果您拥有的那些功能没有相应地遵守这样的规范,那么这很糟糕。最好摆脱它们(至少,隐藏它们并且只导出具有统一行为的重新定义的版本)。或者告诉用户他们必须忍受查找每个人的规范。但不要试图以某种方式绕过定义被破坏的这种特殊症状。

您可以应用一个简单的更改来“标记”失败意味着相反的函数,否则它会返回这样一个包装的结果:

newtype Anti a = Anti { profail :: a }

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

1头脑:可能非常抽象意义上的“同一件事”。 Left 没有必要成为一个普遍的“失败构造函数”,很明显它是与第一个类型参数相关联的 变体构造函数就足够了,这 不是什么是仿函数/ monad 实例在上运行——由此自动得出这将“意味着”在 monadic 应用程序中失败。显然,当你只是 tossing around booleans 时,情况正好相反,所以也许你应该完全摆脱那些......

【讨论】:

  • 请注意,base 4.8 将 Alt 类型添加到 Data.Monoid(将 Alternative 转换为 Monoid),因此这可能不是最好的选择。
  • @og_loc:一元操作不应该输出布尔值来表示失败,就像普通函数一样。对于后者,您使用合适的 Maybe 或 Either 包装器;前者应该使用相应的 monad 转换器来完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-29
  • 1970-01-01
  • 2015-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多