简体   繁体   English

Haskell 中的“Just”语法是什么意思?

[英]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 是自动导入每个模块的标准库。

What Maybe is, Structurally结构上的可能是什么

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

Constructing and Destructing构造和破坏

The type has two constructors, Just a and Nothing .该类型有两个构造函数, Just aNothing 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.对于这种类型,值是通过JustNothing构造的,没有其他(非错误)可能性。

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没有参数类型,因此当它用作构造函数时,它会命名一个常量值,该常量值是所有类型aMaybe 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构造函数确实有一个类型参数,这意味着当用作构造函数时,它就像一个从类型aMaybe 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构造函数的参数。

What Maybe Means也许意味着什么

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 类型不是解决这个问题的唯一方法,但它已被证明是一种有效的方法。

Maybe as a Functor也许作为函子

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 类型类背后的概念,称为FunctorMaybe 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 Integerm_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一次。

Maybe as a Monad也许作为一个单子

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值的计算,所以它有点像计算中间的立即中止或无价值返回。

You Could Have Written 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.给定类型tJust 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.再举一个例子,这可以用在除法中,定义一个除法函数,它接受ab ,如果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:其他处理非全功能的设计:

  • throw exceptions upon checking input value does not fit the range检查输入值不符合范围时抛出异常
  • return a special value (primitive type): favourite choice is a negative value for integer functions that are meant to return Natural numbers (for example, String.indexOf - when a substring is not found, the returned index is commonly designed to be negative)返回一个特殊值(原始类型):最喜欢的选择是用于返回自然数的整数函数的负值(例如,String.indexOf - 当未找到子字符串时,返回的索引通常设计为负数)
  • return a special value (pointer): NULL or some such返回一个特殊值(指针):NULL 或类似的
  • silently return without doing anything: for example, lend could be written to return old balance, if the condition for lending is not met无所事事地默默归还:例如,如果不满足借贷条件,可以写lend来归还旧余额
  • return a special value: Nothing (or Left wrapping some error description object)返回一个特殊值:Nothing(或左包装一些错误描述对象)

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 aEither 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)必须具有相同类型的ifTrueifFalse

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM