Haskell中“ Just”语法的含义是什么?


118

我在互联网上搜寻了此关键字的实际解释。我看过的每个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意味着什么,将不胜感激。

Answers:


211

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

结构上可能是什么

定义看起来像这样:

data Maybe a = Just a
             | Nothing

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

构造与破坏

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

由于Nothing没有参数类型,因此当它用作构造函数时,将命名一个常量值,该值是Maybe a所有类型的类型成员a。但是Just构造函数确实具有类型参数,这意味着当用作构造函数时,它的行为就像从type 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。如果第二个匹配,则还会valJust构造要与之匹配的值时将名称绑定到传递给构造函数的参数。

可能是什么意思

也许您已经熟悉了它的工作方式。Maybe值实际上没有任何魔术,它只是普通的Haskell代数数据类型(ADT)。但是它使用了很多,因为它可以有效地“提升”或扩展类型(例如,Integer从您的示例中)到新的上下文中,在该上下文中,它具有Nothing表示缺乏价值的额外值()!那么该类型的系统要求您检查额外的价值之前,它会让你在Integer可能是在那里。这样可以防止大量的错误。

今天,许多语言都通过NULL引用来处理这种“无值”值。著名的计算机科学家Tony Hoare(他发明了Quicksort,是图灵奖的获得者),将其归结为他的“十亿美元的错误”。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 Integerm_x和一个Int -> Int函数f,则可以fmap f m_x将函数f直接应用于,Maybe Integer而不必担心它是否真正有值。实际上,您可以将整个提升Integer -> Integer函数链应用于Maybe Integer值,而不必担心Nothing在完成时显式检查一次。

也许是单子

我不确定您对a的概念是否熟悉Monad,但是您至少已经使用IO a过,而且类型签名IO a看起来与十分相似Maybe a。尽管IO它的特殊之处在于它不会向您公开其构造函数,因此只能由Haskell运行时系统“运行”,但它还是a Functor的补充Monad。实际上,从某种意义上讲,a Monad只是一种特殊的东西,Functor具有一些额外的功能,但这并不是进入其中的地方。

无论如何,Monads会将IO映射类型喜欢为表示“导致结果的计算”的新类型,您可以Monad通过一个非常类似fmap的函数将函数提升为类型,该函数 liftM将常规函数转换为“通过计算求值而得到值的计算”功能。”

您可能已经猜到了(如果您已经读到了这本书),那Maybe也是一个Monad。它表示“可能无法返回值的计算”。就像fmap示例一样,这使您可以进行大量计算,而不必在每个步骤之后都明确检查错误。实际上,Monad实例的构造方式是,一旦遇到a,就停止Maybe值的计算,因此,这就像在计算过程中立即中止或无值返回。Nothing

你可能已经写过

就像我之前说过的那样,Maybe语言语法或运行时系统中没有任何类型固有的东西。如果Haskell默认不提供它,则您可以自己提供其所有功能!实际上,您仍然可以自己重新编写它,使用不同的名称,并获得相同的功能。

希望您现在就了解了Maybe类型及其构造函数,但是如果仍然不清楚,请告诉我!


19
多么出色的答案!应该提到的是,Haskell经常Maybe在其他语言会使用null或使用的地方使用nil((NullPointerException在每个角落都潜伏着)。现在其他语言也开始使用此构造:Scala as Option,甚至Java 8都具有该Optional类型。
Landei 2013年

3
这是一个很好的解释。我读过的许多解释都暗示了Just是Maybe类型的构造函数的想法,但是没有一个真正使它明确。
reem 2013年

@Landei,谢谢你的建议。我进行了编辑,以提及空引用的危险。
维·皮尔森

@Landei自从70年代的ML以来,option类型就已经存在了,因为Scala使用ML的命名约定,Option带有构造函数,有些则没有,因此Scala很有可能从那里开始使用。
stonemetal 2013年

@Landei苹果公司的Swift也充分利用了可选项
Jamin

37

当前大多数答案都是关于技术Just和朋友工作方式的高度技术性解释。我以为我可以尝试解释它的用途。

null至少对于某些类型,许多语言都有这样的值可以代替真实值使用。这使许多人非常生气,被普遍认为是一个坏举动。尽管如此,有时还是需要一个值null来表明事物的缺失。

Haskell通过使您明确标记可以使用的位置Nothing(版本null)来解决此问题。基本上,如果您的函数正常返回类型Foo,则应返回type Maybe Foo。如果要表明没有值,请返回Nothing。如果要返回值bar,则应返回Just bar

因此,基本上,如果您不能拥有Nothing,则不需要Just。如果可以的话Nothing,您确实需要Just

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


1
除了其他答案,非常好的答案,但是我认为它仍然可以从代码示例中受益:)
PascalVKooten

13

在给定类型的情况下t,的值Just t是type的现有值t,其中Nothing表示无法达到某个值,或者具有该值将无意义的情况。

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

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


1
因此,让我们说在上面的代码中,我删除了Just,为什么这样不起作用?您是否想在任何时候都拥有Just?如果表达式中的函数不返回任何值,该表达式将如何处理?
2013年

7
如果您删除了Just,则您的代码将不会进行类型检查。的原因Just是要维护适当的类型。有一个类型(实际上是monad,但更容易认为是类型)Maybe t,它由形式为Just t和的元素组成Nothing。由于Nothing具有type Maybe t,因此不能正确输入可以求Nothing值为type或其中一些值的表达式t。如果某个函数Nothing在某些情况下返回,则使用该函数的任何表达式都必须具有某种方式进行检查(isJust或case语句),以便处理所有可能的情况。
qaphla

2
因此Just只是在Maybe类型中保持一致,因为常规t不在Maybe类型中。现在一切都更加清晰了。谢谢!
2013年

3
@qaphla:您对它是“一个单子,实际上是[...]”的评论具有误导性。Maybe t 只是一个类型。存在一个Monad实例的Maybe事实不会将其转换为非类型的东西。
莎拉(Sarah)

2

总计函数a-> b可以为类型a的每个可能值找到类型b的值。

在Haskell中,并非所有功能都是合计的。在这种特殊情况下,函数lend不是总计-未针对余额少于储备金的情况进行定义(不过,按我的观点,不允许newBalance少于储备金更有意义-可以从中借入101余额100)。

处理非全部功能的其他设计:

  • 在检查输入值不适合范围时引发异常
  • 返回一个特殊值(原始类型):对于要返回自然数的整数函数,最喜欢的选择是一个负值(例如,String.indexOf-当未找到子字符串时,返回索引通常设计为负数)
  • 返回一个特殊值(指针):NULL或类似的值
  • 不做任何事而默默地退货:例如lend,如果不满足贷款条件,可以写成退还旧余额
  • 返回一个特殊值:Nothing(或左包裹一些错误描述对象)

这些是无法强制执行全部功能的语言的必要设计限制(例如,Agda可以,但是会导致其他复杂性,例如变图不完整)。

返回特殊值或引发异常的问题在于,调用者很容易错误地忽略对这种可能性的处理。

静默丢弃故障的问题也很明显-您正在限制调用者对该函数的操作。例如,如果lend返回了旧余额,则呼叫者无法知道余额是否已更改。根据预期的目的,可能是问题,也可能不是问题。

Haskell的解决方案迫使部分函数的调用者处理类似的类型Maybe a,或者Either error a由于该函数的返回类型。

lend定义的这种方法并不总是计算新余额的功能-在某些情况下未定义新余额。我们可以通过返回特殊值Nothing或将新余额包装在Just中来向呼叫者发出这种情况的信号。呼叫者现在可以自由选择:要么以特殊方式处理失败的放款,要么忽略并使用旧的余额-例如,maybe oldBalance id $ lend amount oldBalance


-1

功能if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)必须有相同类型的ifTrueifFalse

因此,当我们编写时then Nothing,我们必须使用Maybe ainelse f

if balance < reserve
       then (Nothing :: Maybe nb)         -- same type
       else (Just newBalance :: Maybe nb) -- same type

1
我确定您想在这里说些很深的话
sehe 2013年

1
Haskell具有类型推断。无需明确声明Nothing和的类型Just newBalance
2013年

对于未开始的解释,显式类型阐明了的含义 Just
菲利普斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.