【问题标题】:Why would I ever want to use Maybe instead of a List?为什么我要使用 Maybe 而不是 List?
【发布时间】:2016-08-13 21:12:21
【问题描述】:

既然 Maybe 类型与空列表和单例列表的集合同构,那么当我可以使用列表来适应缺席时,为什么还有人想要使用 Maybe 类型?

【问题讨论】:

  • 一个地方来寻找我们为什么关心区分同构类型的潜在动机是人们为什么使用和关心newtype。
  • 我会问“当您只需要Maybe 时,为什么要使用列表?” Maybe 更具体,并且可以防止许多错误,因为您甚至不能编写错误的代码。它还使您的意图在类型签名中更加清晰。在您可以使用Maybe 的情况下(除了可能是非常简短的中间形式),我真的想不出使用列表的好处。是什么引发了这个问题?
  • 使用列表代替Maybe 的一个例子是在“Haskell 编程”一书中,其中展示了如何从头开始实现Parser monad(类似于ReadP ),并且它的类型为String -> [(a, String)],这很奇怪,因为该列表永远是空的或单例的。不知道为什么会这样,只是举个例子

标签: list haskell types maybe


【解决方案1】:

即使是同构的,例如由于搜索空间的增加,QuickCheck 的运行速度会变慢。

【讨论】:

    【解决方案2】:

    这里有几个答案提到详尽无遗。我认为这是一个因素,但不是最大的因素,因为有一种方法可以始终将列表视为Maybes,listToMaybe 函数说明了这一点:

    listToMaybe :: [a] -> Maybe a
    listToMaybe [] = Nothing
    listToMaybe (a:_) = Just a
    

    这是一个详尽的模式匹配,排除了任何直接错误。

    我要强调的更大的因素是,通过使用更精确地模拟代码行为的类型,您可以消除如果使用更通用的替代方案可能出现的潜在行为。例如,假设您的代码中有一些上下文,您使用a -> [b] 形式的类型,尽管唯一正确的替代方案(给定您的程序规范)是空列表或单例列表。尽你所能去强制执行这个上下文应该遵守那个规则的约定,你仍然有可能搞砸并且:

    1. 在该上下文中使用的函数会以某种方式生成包含两个或多个项目的列表;
    2. 不知何故,使用在该上下文中生成的结果的函数将观察列表是否有两个或多个项目,并在这种情况下表现不正确。
      • 示例:某些期望值不超过一个的代码会盲目地打印列表的内容,从而在应该只打印一个项目时打印多个项目。

    但是如果你使用Maybe,那么真的必须要么只有一个值,要么没有,编译器会强制执行。

    【讨论】:

      【解决方案3】:

      Lisp、Scheme、Python、Ruby、JavaScript 等,每个都只能使用一种类型,你可以在 Haskell 中用一个大和类型来表示。每个处理 JavaScript(或其他)值的函数都必须准备好接收数字、字符串、函数、文档对象模型的一部分等,并在遇到意外情况时抛出异常。使用 Haskell 等类型语言编程的人更喜欢限制可能发生的意外事件的数量。他们还喜欢使用类型来表达想法,使类型成为有用的(和机器检查的)文档。类型越接近表示预期的含义,它们就越有用。

      【讨论】:

      • 我的想法,没错。
      【解决方案4】:

      在现有(和正确)答案的基础上,我将提到一个基于 typeclass 的答案。

      不同的类型传达不同的意图 - 返回 Maybe a 表示可能失败的计算,而 [a] 可能表示不确定性(或者,更简单地说,多个可能的返回值) .

      这体现了不同类型具有不同类型类实例的事实——这些实例迎合了类型所传达的基本本质。以Alternative 及其运算符(<|>) 为例,它表示在给定参数之间组合(或选择)的含义。

      • Maybe a 组合可能失败的计算只是意味着取第一个不是Nothing
      • [a] 结合两个计算,每个计算都有多个返回值,这意味着将所有可能的值连接在一起。

      然后,根据您的函数使用的类型,(<|>) 的行为会有所不同。当然,您可以争辩说您不需要 (<|>) 或类似的东西,但是您错过了 Haskell 的主要优势之一:它是许多高级组合库。

      作为一般规则,我们希望我们的类型尽可能贴身且直观。这样一来,我们就不会与标准库作斗争,而且我们的代码更具可读性。

      【讨论】:

      • 作为一个具体的例子,Just x <|> Just y == Just x,但是[x] <|> [y] == [x, y]。
      • 这似乎不是一个理想的例子,因为如果(像 OP 一样)你只关注列表的头部,它们的行为相似。我现在想到的例子是Monoid,但这是一个糟糕的例子,因为Monoid (Maybe a) 实例设计错误。
      • @dfeuer 是的,这个例子可能会更好。我想不出其他足够简单的东西(Functor、Applicative、Monad 的行为方式相同,Monoid 还需要另一个 Monoid)。如果你有更好的想法,欢迎评论!
      【解决方案5】:

      您应该努力拥有准确的类型。 Maybe 表示只有一个值或没有。许多命令式语言通过值 null 表示“无”情况。

      如果您选择列表而不是也许,您的所有函数都将面临获得包含多个成员的列表的可能性。可能它们中的许多只会为一个值定义,并且必须在模式匹配时失败。通过使用Maybe,您可以完全避免一类运行时错误。

      【讨论】:

        【解决方案6】:

        因为如果您将列表与 [] 和 [x] 模式匹配,这不是一个详尽的匹配,您会收到一个警告,迫使您添加另一个永远不会被调用的案例或忽略警告。

        但是,将 Maybe 与 Nothing 和 Just x 进行匹配是详尽无遗的。因此,只有当您未能匹配其中一种情况时,您才会收到警告。

        如果您选择的类型只能表示您可能实际产生的值,您可以依靠非详尽警告来告诉您代码中的错误,您忘记检查给定的案例。如果您选择更多“宽松”类型,您将始终需要考虑警告是否代表实际错误或只是不可能的情况。

        【讨论】:

        • 是的,您完全可以无视警告并使用具有非详尽模式的“分配”(如果它们在域中不详尽,则不能将它们称为函数/映射)。但是,您会使用 Maybe 类型的情况是(据我所见),在这些情况下,可以轻松或毫不费力地保证使用非映射分配不会给您带来麻烦。
        • 也就是说,我还是一个初出茅庐的haskeller,所以我的经验可能只是还没有扩展到这种有问题的情况
        • @Tshimanga 虽然您可以忽略这些警告,但编写完全覆盖其域的函数通常很有用,尤其是在使用面向公众的 API 编写内容时(例如包裹); Maybe 帮助编写语义上有意义的代码来实现这一点。
        【解决方案7】:

        因为存在无限数量的可能列表,而 Maybe 类型的可能值数量有限。它完美地代表了一件事情或没有任何其他可能性的事情。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-12-07
          • 2017-11-07
          • 1970-01-01
          • 2017-03-27
          • 1970-01-01
          • 1970-01-01
          • 2011-06-18
          相关资源
          最近更新 更多