我已经在互联网上搜索了这个关键字的实际解释。我看过的每个 Haskell 教程都只是随机使用它,从不解释它的作用(我看过很多)。
下面是 Real World Haskell 中使用 Just
的一段基本代码。我了解代码的作用,但不了解 Just
的用途或功能是什么。
lend amount balance = let reserve = 100
newBalance = balance - amount
in if balance < reserve
then Nothing
else Just newBalance
根据我的观察,它与 Maybe
打字有关,但这几乎是我所学到的全部内容。
非常感谢对 Just
含义的一个很好的解释。
它实际上只是一个普通的数据构造函数,恰好在 Prelude 中定义,Prelude 是自动导入每个模块的标准库。
结构上的可能是什么
定义看起来像这样:
data Maybe a = Just a
| Nothing
该声明定义了一个类型 Maybe a
,它由类型变量 a
参数化,这意味着您可以将它与任何类型一起使用来代替 a
。
构造和破坏
该类型有两个构造函数,Just a
和 Nothing
。当一个类型有多个构造函数时,这意味着该类型的值必须只使用一个可能的构造函数来构造。对于这种类型,值是通过 Just
或 Nothing
构造的,没有其他(非错误)可能性。
由于 Nothing
没有参数类型,因此当它用作构造函数时,它会命名一个常量值,该常量值是所有类型 a
的类型 Maybe a
的成员。但是 Just
构造函数确实有一个类型参数,这意味着当用作构造函数时,它就像一个从类型 a
到 Maybe a
的函数,即它具有类型 a -> Maybe a
因此,一个类型的构造函数构建了该类型的值;事情的另一面是当你想使用那个值时,这就是模式匹配发挥作用的地方。与函数不同,构造函数可用于模式绑定表达式,这是您可以对属于具有多个构造函数的类型的值进行案例分析的方式。
为了在模式匹配中使用 Maybe a
值,您需要为每个构造函数提供一个模式,如下所示:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
在这种情况下,如果值为 Nothing
,则第一个模式将匹配,如果该值是用 Just
构造的,则第二个模式将匹配。如果第二个匹配,它还将名称 val
绑定到在构造您要匹配的值时传递给 Just
构造函数的参数。
也许意味着什么
也许您已经熟悉它是如何工作的; Maybe
值实际上并没有什么魔力,它只是一个普通的 Haskell 代数数据类型 (ADT)。但是它被使用了很多,因为它有效地将类型(例如您的示例中的 Integer
)“提升”或扩展到一个新的上下文中,其中它有一个表示缺乏价值的额外值(Nothing
)!然后,类型系统要求您检查该额外值,然后它才能让您到达 可能 的 Integer
。这可以防止大量错误。
今天的许多语言通过 NULL 引用处理这种“无价值”值。 Tony Hoare 是一位杰出的计算机科学家(他发明了快速排序,并且是图灵奖的获得者),他将此作为他的 "billion dollar mistake"。 Maybe 类型不是解决这个问题的唯一方法,但它已被证明是一种有效的方法。
也许作为函子
将一种类型转换为另一种类型以便对旧类型的操作也可以转换为适用于新类型的想法是称为 Functor
的 Haskell 类型类背后的概念,Maybe a
有一个有用的实例。
Functor
提供了一种称为 fmap
的方法,该方法将范围从基本类型(例如 Integer
)的值的函数映射到范围从提升的类型(例如 Maybe Integer
)的值的函数。使用 fmap
转换以处理 Maybe
值的函数的工作方式如下:
case maybeVal of
Nothing -> Nothing -- there is nothing, so just return Nothing
Just val -> Just (f val) -- there is a value, so apply the function to it
因此,如果您有一个 Maybe Integer
值 m_x
和一个 Int -> Int
函数 f
,您可以执行 fmap f m_x
将函数 f
直接应用于 Maybe Integer
而不必担心它是否真的有一个值或不是。事实上,您可以将整个提升的 Integer -> Integer
函数链应用于 Maybe Integer
值,并且只需要担心在完成后显式检查 Nothing
一次。
也许作为一个单子
我不确定您对 Monad
的概念有多熟悉,但您之前至少使用过 IO a
,并且类型签名 IO a
看起来与 Maybe a
非常相似。尽管 IO
的特殊之处在于它不会向您公开其构造函数,因此只能由 Haskell 运行时系统“运行”,但它除了是 Monad
之外,还是一个 Functor
。事实上,Monad
只是一种特殊类型的 Functor
,具有一些额外的功能,这有一种重要意义,但这不是深入探讨的地方。
无论如何,像 IO
这样的 Monad 将类型映射到表示“产生值的计算”的新类型,并且您可以通过一个非常类似于 fmap
的函数将函数提升为 Monad
类型,该函数称为 liftM
将常规函数转换为一种“通过评估函数获得的值的计算”。
您可能已经猜到(如果您已经读到这里的话)Maybe
也是一个 Monad
。它表示“可能无法返回值的计算”。就像 fmap
示例一样,这使您可以进行大量计算,而无需在每一步之后显式检查错误。事实上,Monad
实例的构造方式是,在遇到 Nothing
时,对 Maybe
值的计算停止,所以它有点像立即中止或无值在计算过程中返回。
你可以写也许
就像我之前所说的,Maybe
类型没有任何内在的东西融入到语言语法或运行时系统中。如果 Haskell 默认没有提供它,你可以自己提供它的所有功能!事实上,无论如何你都可以自己重新编写它,使用不同的名称,并获得相同的功能。
希望您现在了解 Maybe
类型及其构造函数,但如果仍有不清楚的地方,请告诉我!
当前的大多数答案都是对 Just
和朋友如何工作的高度技术性的解释;我想我可以试着解释一下它的用途。
很多语言都有像 null
这样的值,可以用来代替实际值,至少对于某些类型是这样。 This has made a lot of people very angry and been widely regarded as a bad move. 不过,有时使用像 null
这样的值来指示某事物不存在是很有用的。
Haskell 通过明确标记可以拥有 Nothing
(它的 null
版本)的位置来解决这个问题。基本上,如果您的函数通常会返回类型 Foo
,那么它应该返回类型 Maybe Foo
。如果要指示没有值,请返回 Nothing
。如果要返回值 bar
,则应改为返回 Just bar
。
所以基本上,如果你不能有 Nothing
,你就不需要 Just
。如果您可以拥有 Nothing
,那么您确实需要 Just
。
Maybe
没有什么神奇之处;它建立在 Haskell 类型系统之上。这意味着您可以使用所有常用的 Haskell pattern matching 技巧。
给定类型 t
,值 Just t
是类型 t
的现有值,其中 Nothing
表示未能达到值,或者具有值将毫无意义的情况。
在您的示例中,负余额没有意义,因此如果发生这种情况,则将其替换为 Nothing
。
再举一个例子,这可以用在除法中,定义一个除法函数,它接受 a
和 b
,如果 b
非零则返回 Just a/b
,否则返回 Nothing
。它经常像这样使用,作为异常的方便替代方案,或者像您之前的示例一样,替换没有意义的值。
Just
,您的代码将不会进行类型检查。 Just
的原因是为了维护正确的类型。有一种类型(实际上是一个 monad,但更容易将其视为一种类型)Maybe t
,它由 Just t
和 Nothing
形式的元素组成。由于 Nothing
具有 Maybe t
类型,因此无法正确键入可以计算为 Nothing
或某个 t
类型值的表达式。如果某个函数在某些情况下返回 Nothing
,则使用该函数的任何表达式都必须有某种方法来检查它(isJust
或 case 语句),以便处理所有可能的情况。
Maybe t
是 只是一种类型。 Maybe
有一个 Monad
实例这一事实并没有将其转换为非类型的东西。
总函数 a->b 可以为每个可能的类型 a 的值找到类型 b 的值。
在 Haskell 中,并非所有功能都是完整的。在这种特殊情况下,函数 lend
不是全部的——它不是为余额小于储备的情况定义的(尽管,在我看来,不允许 newBalance 小于储备更有意义——因为,你可以从余额 100 中借 101)。
其他处理非全功能的设计:
检查输入值不符合范围时抛出异常
返回一个特殊值(原始类型):最喜欢的选择是用于返回自然数的整数函数的负值(例如,String.indexOf - 当未找到子字符串时,返回的索引通常设计为负数)
返回一个特殊值(指针):NULL 或类似的
无所事事地默默归还:例如,如果不满足借贷条件,可以写借贷来归还旧余额
返回一个特殊值:Nothing(或左包装一些错误描述对象)
这些是无法强制执行全部功能的语言中的必要设计限制(例如,Agda 可以,但这会导致其他复杂性,例如变得图灵不完整)。
返回特殊值或抛出异常的问题在于调用者很容易错误地忽略对这种可能性的处理。
默默地丢弃失败的问题也很明显——你限制了调用者可以用这个函数做什么。例如,如果 lend
返回旧余额,则调用者无法知道余额是否已更改。这可能是也可能不是问题,具体取决于预期目的。
由于函数的返回类型,Haskell 的解决方案强制部分函数的调用者处理像 Maybe a
或 Either error a
这样的类型。
这样定义的 lend
是一个并不总是计算新余额的函数 - 在某些情况下没有定义新余额。我们通过返回特殊值 Nothing 或将新余额包装在 Just 中向调用者发出这种情况的信号。调用者现在可以自由选择:要么以特殊方式处理借出失败,要么忽略并使用旧余额 - 例如,maybe oldBalance id $ lend amount oldBalance
。
函数 if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
必须具有相同类型的 ifTrue
和 ifFalse
。
所以,当我们写 then Nothing
时,我们必须使用 Maybe a
输入 else f
if balance < reserve
then (Nothing :: Maybe nb) -- same type
else (Just newBalance :: Maybe nb) -- same type
Nothing
和 Just newBalance
的类型。
Just
的使用含义。
Maybe
,而其他语言会使用null
或nil
(每个角落都潜伏着讨厌的NullPointerException
)。现在其他语言也开始使用这种结构:Scala 作为Option
,甚至 Java 8 也将具有Optional
类型。