我已经在网上搜索了这个关键字的实际解释。我看过的每个Haskell教程都是随机开始使用它,而不是解释它的作用(我看过很多)。
这是使用Just
的Real World Haskell的基本代码。我理解代码的作用,但我不明白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
没有参数类型,当它用作构造函数时,它为所有类型Maybe a
命名一个常量值,该值是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是一位杰出的计算机科学家(他发明了Quicksort并且是图灵奖得主),他拥有这个作为他的"billion dollar mistake"。 Maybe类型不是修复此问题的唯一方法,但事实证明它是一种有效的方法。
将一种类型转换为另一种类型的想法使得旧类型上的操作也可以转换为对新类型起作用是Haskell类型类背后的概念,称为Functor
,其中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运行时系统“运行”,它除了作为Functor
之外还是一个Monad
。事实上,有一个重要的意义,其中Monad
只是一种特殊的Functor
,具有一些额外的功能,但这不是进入它的地方。
无论如何,像IO
这样的Monads将类型映射到表示“产生值的计算”的新类型,你可以通过一个名为Monad
的类似fmap
的函数将函数提升为liftM
类型,将常规函数转换为“导致值的计算”通过评估功能获得。“
你可能已经猜到了(如果你已经读过这篇文章)Maybe
也是一个Monad
。它表示“可能无法返回值的计算”。就像fmap
示例一样,这使您可以进行一大堆计算,而无需在每个步骤后显式检查错误。实际上,构造Monad
实例的方式,一旦遇到Maybe
,Nothing
值的计算就会停止,所以它有点像在计算过程中立即中止或无值返回。
就像我之前说过的那样,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
的除法函数,如果Just a/b
非零,则返回b
,否则返回Nothing
。它通常像这样使用,作为异常的替代方法,或者像前面的例子一样,替换没有意义的值。
总函数a-> b可以为类型a的每个可能值找到类型b的值。
在Haskell中,并非所有函数都是完整的。在这个特殊情况下,函数lend
不是全部 - 它没有定义为余额小于保留的情况(尽管根据我的口味,不允许newBalance小于保留更有意义 - 因为,你可以借用101从100的余额)。
其他涉及非总功能的设计:
lend
以返回旧余额这些是不能强制执行全部功能的语言的必要设计限制(例如,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