139

我已经在互联网上搜索了这个关键字的实际解释。我看过的每个 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非常感谢对什么手段的一个很好的解释。

4

5 回答 5

241

它实际上只是一个普通的数据构造函数,恰好在Prelude中定义,Prelude是自动导入每个模块的标准库。

结构上的可能是什么

定义看起来像这样:

data Maybe a = Just a
             | Nothing

该声明定义了一个类型,Maybe a它由一个类型变量参数化a,这意味着您可以将它与任何类型一起使用来代替a.

构造和破坏

该类型有两个构造函数,Just aNothing. 当一个类型有多个构造函数时,这意味着该类型的值必须只使用一个可能的构造函数来构造。对于这种类型,值是通过Just或构造Nothing的,没有其他(非错误)可能性。

由于Nothing没有参数类型,因此当它用作构造函数时,它会命名一个常量值,该常量值是Maybe a所有类型的类型成员a。但是Just构造函数确实有一个类型参数,这意味着当用作构造函数时,它就像一个从 typea到的函数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)

在那个 case 表达式中,如果值是 ,则第一个模式将匹配Nothing,如果值是用 构造的,则第二个模式将匹配Just。如果第二个匹配,它还将名称绑定到在构造您匹配的值时val传递给Just构造函数的参数。

也许意味着什么

也许您已经熟悉它是如何工作的;值并没有真正的魔力Maybe,它只是一个普通的 Haskell 代数数据类型 (ADT)。但是它被使用了很多,因为它有效地“提升”或扩展了一个类型,比如Integer从你的例子中,到一个新的上下文中,在这个上下文中它有一个额外的值(Nothing),代表缺乏价值!然后,类型系统要求您检查该额外值,然后它才能让您获得可能Integer存在的值。这可以防止大量错误。

今天的许多语言通过 NULL 引用处理这种“无价值”值。Tony Hoare 是一位杰出的计算机科学家(他发明了快速排序并且是图灵奖得主),他承认这是他的“十亿美元的错误”。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 Integervaluem_x和一个Int -> Intfunction f,你可以直接fmap f m_x将这个函数应用到 ,而不用担心它是否真的有一个值。事实上,您可以将整个提升函数链应用于值,并且只需要担心在完成后显式检查一次。fMaybe IntegerInteger -> IntegerMaybe IntegerNothing

也许作为一个单子

我不确定您对 a 的概念有多熟悉Monad,但您至少以前使用IO a过,并且类型签名IO a看起来非常类似于Maybe a. 尽管IO它的特殊之处在于它不会向您公开其构造函数,因此只能由 Haskell 运行时系统“运行”,但它仍然是Functor除了Monad. 事实上,在一个重要的意义上,aMonad只是一种Functor具有一些额外功能的特殊类型,但这不是深入探讨的地方。

无论如何,Monads 喜欢IO将类型映射到表示“产生值的计算”的新类型,并且您可以Monad通过一个非常fmap相似的函数 将函数提升为类型,该函数liftM将常规函数转换为“计算结果,通过评估功能。”

你可能已经猜到(如果你已经读到这里的话)这Maybe也是一个Monad. 它表示“可能无法返回值的计算”。就像fmap示例一样,这使您可以进行大量计算,而无需在每一步之后显式检查错误。事实上,Monad实例的构造方式,一旦遇到 a就停止Maybe对值的计算,所以它有点像计算中间的立即中止或无值返回。Nothing

你可以写也许

就像我之前说的,Maybe语言语法或运行时系统中没有任何固有的类型。如果 Haskell 默认没有提供它,你可以自己提供它的所有功能!事实上,无论如何你都可以自己重新编写它,使用不同的名称,并获得相同的功能。

希望您Maybe现在了解类型及其构造函数,但如果仍有任何不清楚的地方,请告诉我!

于 2013-09-15T05:37:19.280 回答
49

当前的大多数答案都是对Just朋友如何工作的高度技术性的解释;我想我可以试着解释一下它的用途。

许多语言都有这样的值null,可以用来代替真正的值,至少对于某些类型是这样。这让很多人非常愤怒,被广泛认为是一个坏举动。尽管如此,有时使用一个值null来表示某事物的缺失还是很有用的。

Haskell 通过明确标记可以拥有 a 的位置Nothing(它的 a 版本null)来解决这个问题。基本上,如果您的函数通常会返回 type Foo,那么它应该返回 type Maybe Foo。如果要表明没有值,请返回Nothing。如果你想返回一个值bar,你应该改为 return Just bar

所以基本上,如果你不能拥有Nothing,你就不需要Just. 如果你有Nothing,你确实需要Just

没有什么神奇的Maybe;它建立在 Haskell 类型系统之上。这意味着您可以使用所有常用的 Haskell模式匹配技巧。

于 2014-03-12T08:14:13.863 回答
15

给定 type t, valueJust t是 type 的现有值t,其中Nothing表示无法达到值,或者具有值将毫无意义的情况。

在您的示例中,负余额没有意义,因此如果发生这种情况,则将其替换为Nothing.

再举一个例子,这可以用在除法中,定义一个除法函数,它接受aand bJust a/b如果b非零则返回,Nothing否则返回。它经常像这样使用,作为异常的方便替代方案,或者像您之前的示例一样,替换没有意义的值。

于 2013-09-15T02:14:02.913 回答
2

A total function a->b can find a value of type b for every possible value of type a.

In Haskell not all functions are total. 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).

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)
  • return a special value (pointer): NULL or some such
  • silently return without doing anything: for example, lend could be written to return old balance, if the condition for lending is not met
  • return a special value: Nothing (or Left wrapping some error description object)

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).

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. 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.

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. We signal this circumstance to the caller by either returning the special value Nothing, or by wrapping the new balance in 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.

于 2013-09-16T12:39:14.920 回答
-1

函数if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)必须具有 和 相同的ifTrue类型ifFalse

所以,当我们写的时候then Nothing,我们必须使用Maybe atype inelse f

if balance < reserve
       then (Nothing :: Maybe nb)         -- same type
       else (Just newBalance :: Maybe nb) -- same type
于 2013-09-15T16:44:02.783 回答