Haskell中“Just”语法意味着什么?

问题描述 投票:104回答:5

我已经在网上搜索了这个关键字的实际解释。我看过的每个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意味着什么的一个很好的解释将非常感激。

haskell syntax
5个回答
187
投票

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

结构上可能是什么

定义看起来像这样:

data Maybe a = Just a
             | Nothing

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

建构与毁灭

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

由于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类型不是修复此问题的唯一方法,但事实证明它是一种有效的方法。

也许作为一个Functor

将一种类型转换为另一种类型的想法使得旧类型上的操作也可以转换为对新类型起作用是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_xInt -> Int函数f,你可以做fmap f m_x将函数f直接应用于Maybe Integer而不用担心它是否真的有值。事实上,你可以将一整套提升的Integer -> Integer函数应用于Maybe Integer值,只需要担心在你完成时明确检查Nothing

也许作为Monad

我不确定你对Monad的概念有多熟悉,但你之前至少使用过IO a,类型签名IO a看起来非常类似于Maybe a。虽然IO的特殊之处在于它不会向你展示它的构造函数,因此只能由Haskell运行时系统“运行”,它除了作为Functor之外还是一个Monad。事实上,有一个重要的意义,其中Monad只是一种特殊的Functor,具有一些额外的功能,但这不是进入它的地方。

无论如何,像IO这样的Monads将类型映射到表示“产生值的计算”的新类型,你可以通过一个名为Monad的类似fmap的函数将函数提升为liftM类型,将常规函数转换为“导致值的计算”通过评估功能获得。“

你可能已经猜到了(如果你已经读过这篇文章)Maybe也是一个Monad。它表示“可能无法返回值的计算”。就像fmap示例一样,这使您可以进行一大堆计算,而无需在每个步骤后显式检查错误。实际上,构造Monad实例的方式,一旦遇到MaybeNothing值的计算就会停止,所以它有点像在计算过程中立即中止或无值返回。

你可能写的也许吧

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

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


35
投票

目前大多数答案都是对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技巧。


13
投票

给定类型tJust t的值是t类型的现有值,其中Nothing表示未达到值,或者具有值无意义的情况。

在你的例子中,负平衡是没有意义的,所以如果发生这样的事情,它将被Nothing取代。

再举一个例子,这可以用于除法,定义一个取ab的除法函数,如果Just a/b非零,则返回b,否则返回Nothing。它通常像这样使用,作为异常的替代方法,或者像前面的例子一样,替换没有意义的值。


2
投票

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

在Haskell中,并非所有函数都是完整的。在这个特殊情况下,函数lend不是全部 - 它没有定义为余额小于保留的情况(尽管根据我的口味,不允许newBalance小于保留更有意义 - 因为,你可以借用101从100的余额)。

其他涉及非总功能的设计:

  • 检查输入值不符合范围时抛出异常
  • 返回一个特殊值(基本类型):最喜欢的选择是整数函数的负值,用于返回自然数(例如,String.indexOf - 当找不到子字符串时,返回的索引通常设计为负数)
  • 返回一个特殊值(指针):NULL或一些这样的
  • 默默地返回而不做任何事情:例如,如果不满足贷款条件,可以写lend以返回旧余额
  • 返回一个特殊值:Nothing(或者包装一些错误描述对象)

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

返回特殊值或抛出异常的问题是调用者很容易忽略错误处理这种可能性。

静默丢弃故障的问题也是显而易见的 - 您限制了呼叫者可以对该功能执行的操作。例如,如果lend返回旧余额,则调用者无法知道余额是否已更改。根据预期目的,它可能是也可能不是问题。

由于函数的返回类型,Haskell的解决方案强制部分函数的调用者处理像Maybe aEither 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 a中使用else f类型

if balance < reserve
       then (Nothing :: Maybe nb)         -- same type
       else (Just newBalance :: Maybe nb) -- same type
© www.soinside.com 2019 - 2024. All rights reserved.