Maybe

随着你更多地使用 Elm,你将开始非常频繁地看到 Maybe 类型。它的定义如下

type Maybe a
  = Just a
  | Nothing

-- Just 3.14 : Maybe Float
-- Just "hi" : Maybe String
-- Just True : Maybe Bool
-- Nothing   : Maybe a

这是一个具有两种变体类型的类型。你可能会得到 Nothing 或者 Just 一个值。类型变量使能根据特定值生成一个 Maybe FloatMaybe String

这在两种主要场景中很方便:部分函数和可选字段。

部分函数

有时,你需要一个针对部分输入提供答案的函数,但对于其他输入则不提供答案。许多人在尝试将用户输入转换成数字时遇到了 String.toFloat 的这种情况。让我们看看实际运行示例

[ { "input": "String.toFloat", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> Maybe Float" }, { "input": "String.toFloat \"3.1415\"", "value": "\u001b[96mJust\u001b[0m \u001b[95m3.1415\u001b[0m", "type_": "Maybe Float" }, { "input": "String.toFloat \"abc\"", "value": "\u001b[96mNothing\u001b[0m", "type_": "Maybe Float" } ]

尝试用其他字符串调用 String.toFloat 来看看它发生了什么 ⬆️

并不是所有的字符串都能够转化为有意义的数字,因此此函数明确地模拟了这一点。字符串可以转换成浮点数吗?Maybe!从那里我们可以在返回的数据上进行模式匹配并根据需要继续执行。

练习:我写了一个小程序 here,可将摄氏度转换为华氏度。尝试以不同的方式重构 view 代码。你能用红色边框标注出无效输入吗?你能添加更多转换吗?华氏度到摄氏度?英寸到米?

可选字段

在具有可选字段的记录中,你通常看到的另一个地方是 Maybe 值。

例如,假设我们在运营一个社交网站。建立人与人之间的联系、友谊等等。您懂的。洋葱早在 2011 年就最直接地概括了我们的真实目标:尽可能为 CIA 挖掘更多数据。如果我们想要获得所有数据,我们需要让人们逐渐接受这一点。让他们稍后添加这些信息。添加功能,鼓励他们随着时间的推移共享越来越多的信息。

因此,我们首先从用户的简单模型开始。他们必须有姓名,但我们可以选择让年龄信息可选。

type alias User =
  { name : String
  , age : Maybe Int
  }

现在,假设 Sue 创建了一个帐户,但决定不提供她的生日。

sue : User
sue =
  { name = "Sue", age = Nothing }

不过,Sue 的朋友无法祝她生日快乐。真想知道他们是否真的关心她……稍后 Tom 创建了一个个人资料,并提供了他的年龄信息。

tom : User
tom =
  { name = "Tom", age = Just 24 }

太棒了,这会在他的生日那天派上用场。但更重要的是,Tom 是一个宝贵人口群体的一部分!广告商会很高兴的。

好的,现在我们有一些用户,那么如何在不违反任何法律的前提下向他们营销酒类?如果我们向 21 岁以下的人群营销产品,人们可能会生气,所以我们先验证一下

canBuyAlcohol : User -> Bool
canBuyAlcohol user =
  case user.age of
    Nothing ->
      False

    Just age ->
      age >= 21

请注意,Maybe 类型强制我们对用户的年龄进行模式匹配。实际上,不可能编写忘记用户可能没有年龄信息的代码。Elm 可以确保这一点!现在,我们可以放心地宣传酒类,因为我们没有直接影响未成年人!只影响了他们的长者。

避免过度使用

Maybe 类型非常有用,但也有一定的限制。初学者尤其容易对 Maybe 感到兴奋,并无处不在地使用它,即使是自定义类型更为合适的情况下也是如此。

例如,假设我们有一个锻炼应用程序,在其中与朋友进行比赛。您从朋友姓名列表开始,但稍后可以加载更多有关他们的健身信息。您可能很想像这样建模

type alias Friend =
  { name : String
  , age : Maybe Int
  , height : Maybe Float
  , weight : Maybe Float
  }

所有信息都在那里,但您并没有真正建模您的特定应用程序的工作方式。像这样建模会更精确

type Friend
  = Less String
  | More String Info

type alias Info =
  { age : Int
  , height : Float
  , weight : Float
  }

此新模型捕获了有关您的应用程序的更多信息。只有两种实际情况。要么只有姓名信息,要么有姓名和一堆信息。在您的视图代码中,您只需考虑是显示朋友的 Less 视图还是 More 视图。您不必回答“如果我有 age 信息但没有 weight 信息,会怎样?”这样的问题。使用我们更精确的类型,这是不可能的!

重点在于,如果您发现自己在到处使用 Maybe,那么值得检查一下您的 typetype alias 定义,看看能否找到更精确的表示法。这通常会在您的更新和视图代码中引入大量优化!

旁注:连接到 null 引用

null 引用发明者 Tony Hoare 描述说

我称它为我价值十亿美元的错误。那是 1965 年发明 null 引用的时候。那时候,我正在设计一种面向对象语言(ALGOL W)中的第一个全面类型系统。我的目标是确保所有对引用的使用都是绝对安全的,并由编译器自动执行检查。但由于它很容易实现,我就无法抗拒诱惑,放进了空引用。这导致了无数错误、漏洞和系统崩溃,在过去的四十年中可能已造成了 10 亿美元的损失和损坏。

该设计使失败成为隐含的。任何时候你认为自己拥有一个 String 时,你可能都只会拥有一个 null。你应该检查一下吗?给你值的人是否检查过?也许这样不会出错?或者可能导致服务器崩溃?我猜我们以后会知道的!

Elm 避免了这些问题,因为它根本就没有 null 引用。我们使用 Maybe 这样的自定义类型来转换失败为显式的。这样就没有意外发生。String 始终是 String,当看到 Maybe String 时,编译器将确保这两个变量都已考虑在内。这样你便拥有了同样的灵活性,但也无需再担心意外崩溃。

与“”匹配的个结果

    没有与“”匹配的结果