[英]What does the "Just" syntax mean in Haskell?
I have scoured the internet for an actual explanation of what this keyword does.我已经在互联网上搜索了这个关键字的实际解释。 Every Haskell tutorial that I have looked at just starts using it randomly and never explains what it does (and I've looked at many).
我看过的每个 Haskell 教程都只是随机使用它,从不解释它的作用(我看过很多)。
Here's a basic piece of code from Real World Haskell that uses Just
.这是来自Real World Haskell的一段基本代码,它使用
Just
。 I understand what the code does, but I don't understand what the purpose or function of Just
is.我了解代码的作用,但我不了解
Just
的目的或功能是什么。
lend amount balance = let reserve = 100
newBalance = balance - amount
in if balance < reserve
then Nothing
else Just newBalance
From what I have observed, it is related to Maybe
typing, but that's pretty much all I have managed to learn.根据我的观察,它与
Maybe
打字有关,但这几乎是我学到的全部内容。
A good explanation of what Just
means would be very much appreciated.非常感谢对
Just
的含义的一个很好的解释。
It's actually just a normal data constructor that happens to be defined in the Prelude , which is the standard library that is imported automatically into every module.它实际上只是一个普通的数据构造函数,恰好在Prelude中定义,Prelude 是自动导入每个模块的标准库。
The definition looks something like this:定义看起来像这样:
data Maybe a = Just a
| Nothing
That declaration defines a type, Maybe a
, which is parameterized by a type variable a
, which just means that you can use it with any type in place of a
.该声明定义了一个类型
Maybe a
,它由类型变量a
参数化,这意味着您可以将它与任何类型一起使用来代替a
。
The type has two constructors, Just a
and Nothing
.该类型有两个构造函数,
Just a
和Nothing
。 When a type has multiple constructors, it means that a value of the type must have been constructed with just one of the possible constructors.当一个类型有多个构造函数时,这意味着该类型的值必须只使用一个可能的构造函数来构造。 For this type, a value was either constructed via
Just
or Nothing
, there are no other (non-error) possibilities.对于这种类型,值是通过
Just
或Nothing
构造的,没有其他(非错误)可能性。
Since Nothing
has no parameter type, when it's used as a constructor it names a constant value that is a member of type Maybe a
for all types a
.由于
Nothing
没有参数类型,因此当它用作构造函数时,它会命名一个常量值,该常量值是所有类型a
的Maybe a
类型的成员。 But the Just
constructor does have a type parameter, which means that when used as a constructor it acts like a function from type a
to Maybe a
, ie it has the type a -> Maybe a
但是
Just
构造函数确实有一个类型参数,这意味着当用作构造函数时,它就像一个从类型a
到Maybe a
的函数,即它的类型a -> Maybe a
So, the constructors of a type build a value of that type;因此,一个类型的构造函数构建了该类型的值; the other side of things is when you would like to use that value, and that is where pattern matching comes in to play.
事情的另一面是当你想使用那个值时,这就是模式匹配发挥作用的地方。 Unlike functions, constructors can be used in pattern binding expressions, and this is the way in which you can do case analysis of values that belong to types with more than one constructor.
与函数不同,构造函数可用于模式绑定表达式,这是您可以对属于具有多个构造函数的类型的值进行案例分析的方式。
In order to use a Maybe a
value in a pattern match, you need to provide a pattern for each constructor, like so:为了在模式匹配中使用
Maybe a
值,您需要为每个构造函数提供一个模式,如下所示:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
In that case expression, the first pattern would match if the value was Nothing
, and the second would match if the value was constructed with Just
.在这种情况下,如果值为
Nothing
,则第一个模式将匹配,如果该值是用Just
构造的,则第二个模式将匹配。 If the second one matches, it also binds the name val
to the parameter that was passed to the Just
constructor when the value you're matching against was constructed.如果第二个匹配,它还将名称
val
绑定到在构造您匹配的值时传递给Just
构造函数的参数。
Maybe you were already familiar with how this worked;也许您已经熟悉它是如何工作的; there's not really any magic to
Maybe
values, it's just a normal Haskell Algebraic Data Type (ADT). Maybe
值并没有真正的魔力,它只是一个普通的 Haskell 代数数据类型 (ADT)。 But it's used quite a bit because it effectively "lifts" or extends a type, such as Integer
from your example, into a new context in which it has an extra value ( Nothing
) that represents a lack of value!但是它被使用了很多,因为它有效地“提升”或扩展了一个类型,例如您的示例中的
Integer
,到一个新的上下文中,其中它有一个额外的值( Nothing
),表示缺乏价值! The type system then requires that you check for that extra value before it will let you get at the Integer
that might be there.然后,类型系统要求您检查该额外值,然后它才能让您获得可能存在的
Integer
。 This prevents a remarkable number of bugs.这可以防止大量错误。
Many languages today handle this sort of "no-value" value via NULL references.今天的许多语言通过 NULL 引用处理这种“无价值”值。 Tony Hoare, an eminent computer scientist (he invented Quicksort and is a Turing Award winner), owns up to this as his "billion dollar mistake" .
Tony Hoare 是一位杰出的计算机科学家(他发明了快速排序并且是图灵奖得主),他承认这是他的“十亿美元的错误” 。 The Maybe type is not the only way to fix this, but it has proven to be an effective way to do it.
Maybe 类型不是解决这个问题的唯一方法,但它已被证明是一种有效的方法。
The idea of transforming one type to another one such that operations on the old type can also be transformed to work on the new type is the concept behind the Haskell type class called Functor
, which Maybe a
has a useful instance of.将一种类型转换为另一种类型以便对旧类型的操作也可以转换为对新类型起作用的想法是 Haskell 类型类背后的概念,称为
Functor
, Maybe a
有一个有用的实例。
Functor
provides a method called fmap
, which maps functions that range over values from the base type (such as Integer
) to functions that range over values from the lifted type (such as Maybe Integer
). Functor
提供了一个名为fmap
的方法,该方法将范围从基本类型(例如Integer
)的值映射到范围从提升类型(例如Maybe Integer
)的值的函数。 A function transformed with fmap
to work on a Maybe
value works like this:使用
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
So if you have a Maybe Integer
value m_x
and an Int -> Int
function f
, you can do fmap f m_x
to apply the function f
directly to the Maybe Integer
without worrying if it's actually got a value or not.因此,如果您有一个
Maybe Integer
值m_x
和一个Int -> Int
函数f
,您可以执行fmap f m_x
将函数f
直接应用于Maybe Integer
,而不必担心它是否真的有一个值。 In fact, you could apply a whole chain of lifted Integer -> Integer
functions to Maybe Integer
values and only have to worry about explicitly checking for Nothing
once when you're finished.实际上,您可以将整个提升的
Integer -> Integer
函数链应用于Maybe Integer
值,并且只需要担心在完成后显式检查Nothing
一次。
I'm not sure how familiar you are with the concept of a Monad
yet, but you have at least used IO a
before, and the type signature IO a
looks remarkably similar to Maybe a
.我不确定您对
Monad
的概念有多熟悉,但您之前至少使用过IO a
,并且类型签名IO a
看起来与Maybe a
非常相似。 Although IO
is special in that it doesn't expose its constructors to you and can thus only be "run" by the Haskell runtime system, it's still also a Functor
in addition to being a Monad
.尽管
IO
的特殊之处在于它不会向您公开其构造函数,因此只能由 Haskell 运行时系统“运行”,但它除了是Monad
之外,还是一个Functor
。 In fact, there's an important sense in which a Monad
is just a special kind of Functor
with some extra features, but this isn't the place to get into that.事实上,
Monad
只是一种特殊的Functor
,具有一些额外的特性,这在某种意义上是很重要的,但这不是深入探讨的地方。
Anyway, Monads like IO
map types to new types that represent "computations that result in values" and you can lift functions into Monad
types via a very fmap
-like function called liftM
that turns a regular function into a "computation that results in the value obtained by evaluating the function."无论如何,Monad 喜欢
IO
将类型映射到表示“产生值的计算”的新类型,并且您可以通过一个非常fmap
的名为liftM
的函数将函数提升为Monad
类型,该函数将常规函数转换为“产生值的计算”通过评估函数获得。”
You have probably guessed (if you have read this far) that Maybe
is also a Monad
.你可能已经猜到(如果你已经读到这里的话)
Maybe
也是一个Monad
。 It represents "computations that could fail to return a value".它表示“可能无法返回值的计算”。 Just like with the
fmap
example, this lets you do a whole bunch of computations without having to explicitly check for errors after each step.就像
fmap
示例一样,这使您可以进行大量计算,而无需在每一步之后显式检查错误。 And in fact, the way the Monad
instance is constructed, a computation on Maybe
values stops as soon as a Nothing
is encountered, so it's kind of like an immediate abort or a valueless return in the middle of a computation.事实上,
Monad
实例的构造方式是,一旦遇到Nothing
就停止对Maybe
值的计算,所以它有点像计算中间的立即中止或无价值返回。
Like I said before, there is nothing inherent to the Maybe
type that is baked into the language syntax or runtime system.就像我之前说的,
Maybe
类型没有任何固有的东西融入到语言语法或运行时系统中。 If Haskell didn't provide it by default, you could provide all of its functionality yourself!如果 Haskell 默认没有提供它,你可以自己提供它的所有功能! In fact, you could write it again yourself anyway, with different names, and get the same functionality.
事实上,无论如何你都可以自己重新编写它,使用不同的名称,并获得相同的功能。
Hopefully you understand the Maybe
type and its constructors now, but if there is still anything unclear, let me know!希望您现在了解
Maybe
类型及其构造函数,但如果还有什么不清楚的地方,请告诉我!
Most of the current answers are highly technical explanations of how Just
and friends work;当前的大多数答案都是对
Just
and friends 如何工作的高度技术性的解释; I thought I might try my hand at explaining what it's for.我想我可以试着解释一下它的用途。
A lot of languages have a value like null
that can be used instead of a real value, at least for some types.许多语言都有一个像
null
这样的值,可以用来代替实际值,至少对于某些类型来说是这样。 This has made a lot of people very angry and been widely regarded as a bad move. 这让很多人非常愤怒,被广泛认为是一个坏举动。 Still, it's sometimes useful to have a value like
null
to indicate the absence of a thing.尽管如此,有时使用
null
之类的值来指示某事物的缺失还是很有用的。
Haskell solves this problem by making you explicitly mark places where you can have a Nothing
(its version of a null
). Haskell 通过明确标记可以拥有
Nothing
的位置(它的null
版本)来解决这个问题。 Basically, if your function would normally return the type Foo
, it instead should return the type Maybe Foo
.基本上,如果您的函数通常会返回类型
Foo
,那么它应该返回类型Maybe Foo
。 If you want to indicate that there's no value, return Nothing
.如果您想表明没有价值,请返回
Nothing
。 If you want to return a value bar
, you should instead return Just bar
.如果你想返回一个值
bar
,你应该返回Just bar
。
So basically, if you can't have Nothing
, you don't need Just
.所以基本上,如果你不能拥有
Nothing
,你就不需要Just
。 If you can have Nothing
, you do need Just
.如果您可以拥有
Nothing
,则确实需要Just
。
There's nothing magical about Maybe
; Maybe
没有什么神奇之处; it's built on the Haskell type system.它建立在 Haskell 类型系统之上。 That means you can use all the usual Haskell pattern matching tricks with it.
这意味着您可以使用所有常用的 Haskell模式匹配技巧。
Given a type t
, a value of Just t
is an existing value of type t
, where Nothing
represents a failure to reach a value, or a case where having a value would be meaningless.给定类型
t
, Just t
的值是类型t
的现有值,其中Nothing
表示无法达到值,或者具有值将毫无意义的情况。
In your example, having a negative balance doesn't make sense, and so if such a thing would occur, it is replaced by Nothing
.在您的示例中,负余额没有意义,因此如果发生这种情况,则将其替换为
Nothing
。
For another example, this could be used in division, defining a division function that takes a
and b
, and returns Just a/b
if b
is nonzero, and Nothing
otherwise.再举一个例子,这可以用在除法中,定义一个除法函数,它接受
a
和b
,如果b
非零则返回Just a/b
,否则返回Nothing
。 It's often used like this, as a convenient alternative to exceptions, or like your earlier example, to replace values that don't make sense.它经常像这样使用,作为异常的方便替代方案,或者像您之前的示例一样,替换没有意义的值。
A total function a->b can find a value of type b for every possible value of type a.总函数 a->b 可以为每个可能的类型 a 的值找到类型 b 的值。
In Haskell not all functions are total.在 Haskell 中,并非所有功能都是完整的。 In this particular case function
lend
is not total - it is not defined for case when balance is less than reserve (although, to my taste it would make more sense to not permit newBalance to be less than reserve - as is, you can borrow 101 from a balance of 100).在这种特殊情况下,函数
lend
不是总的——它不是为余额小于储备的情况定义的(尽管,在我看来,不允许 newBalance 小于储备更有意义——因为你可以借用 101余额为 100)。
Other designs that deal with non-total functions:其他处理非全功能的设计:
lend
could be written to return old balance, if the condition for lending is not metlend
来归还旧余额 These are necessary design limitations in languages that cannot enforce totality of functions (for example, Agda can, but that leads to other complications, like becoming turing-incomplete).这些是无法强制执行全部功能的语言中的必要设计限制(例如,Agda 可以,但这会导致其他复杂性,例如变得图灵不完整)。
The problem with returning a special value or throwing exceptions is that it is easy for the caller to omit handling of such a possibility by mistake.返回特殊值或抛出异常的问题在于调用者很容易错误地忽略对这种可能性的处理。
The problem with silently discarding a failure is also obvious - you are limiting what the caller can do with the function.默默地丢弃失败的问题也很明显——你限制了调用者可以用这个函数做什么。 For example, if
lend
returned old balance, the caller has no way of knowing if balance has changed.例如,如果
lend
返回旧余额,则调用者无法知道余额是否已更改。 It may or may not be a problem, depending on the intended purpose.这可能是也可能不是问题,具体取决于预期目的。
Haskell's solution forces the caller of a partial function to deal with the type like Maybe a
, or Either error a
because of the function's return type.由于函数的返回类型,Haskell 的解决方案强制部分函数的调用者处理诸如
Maybe a
或Either error a
之类的类型。
This way lend
as it is defined, is a function that doesn't always compute new balance - for some circumstances new balance is not defined.这种方式按照定义
lend
,是一个并不总是计算新余额的函数——在某些情况下,新余额没有定义。 We signal this circumstance to the caller by either returning the special value Nothing, or by wrapping the new balance in Just.我们通过返回特殊值 Nothing 或将新余额包装在 Just 中向调用者发出这种情况的信号。 The caller now has freedom to choose: either handle the failure to lend in a special way, or ignore and use old balance - for example,
maybe oldBalance id $ lend amount oldBalance
.调用者现在可以自由选择:要么以特殊方式处理借出失败,要么忽略并使用旧余额 - 例如,
maybe oldBalance id $ lend amount oldBalance
。
Function if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
must have the same type of ifTrue
and ifFalse
.函数
if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
必须具有相同类型的ifTrue
和ifFalse
。
So, when we write then Nothing
, we must use Maybe a
type in else f
所以,当我们写
then Nothing
时,我们必须在else f
中使用Maybe a
类型
if balance < reserve
then (Nothing :: Maybe nb) -- same type
else (Just newBalance :: Maybe nb) -- same type
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.