我可以在Haskell中声明NULL值吗?

6pp0gazn  于 2023-08-06  发布在  其他
关注(0)|答案(4)|浏览(143)

只是好奇,似乎在声明一个名称时,我们总是指定一些有效的值,比如let a = 3。问题是,在命令式语言中,包括c/java,总是有一个关键字“null”。Haskell有类似的功能吗?什么时候函数对象可以为null?

kuuvgm7e

kuuvgm7e1#

这里有一个“null”值,你可以用它来表示任何类型的变量。它被称为(发音为 bottom)。我们不需要一个关键字来产生底部值;实际上是任何不终止的计算的值。比如说

bottom = let x = x in x   -- or simply `bottom = bottom`

字符串
会无限循环故意这样做显然不是一个好主意,但是您可以使用undefined作为“标准底部值”。这可能是Haskell最接近Java的null关键字的东西。
但是对于大多数Java程序员会使用null的应用程序,您绝对不应该/不能使用它。

  • 由于Haskell中的所有内容都是不可变的,因此未定义的值将始终保持未定义状态。这不可能用作“稍等,我稍后定义”指示†。
  • 不可能检查 * 是否 * 一个值是底部或不是。对于rather deep theoretical reasons,事实上。所以你不能用它来表示那些可能被定义或者没有被定义的值。

你知道吗?Haskell不允许这样做真是太好了!在Java中,您需要经常注意值可能为null。在Haskell中,如果一个值是bottom,那么有些东西是明显损坏的,但这永远不会是预期行为的一部分/你可能需要检查的东西。如果对于某个值,它可能没有被定义,那么你必须总是通过将类型 Package 在Maybe中来明确这一点。通过这样做,您可以确保任何试图使用该值的人 * 必须 * 首先检查它是否存在。不可能忘记这一点,并在运行时遇到空引用异常!
由于Haskell非常擅长处理变量类型,因此检查Maybe Package 的值的内容并不太麻烦。你可以直接用模式匹配来做

quun :: Int -> String
quun i = case computationWhichMayFail i of
   Just j  -> show j
   Nothing -> "blearg, failed"

computationWhichMayFail :: Int -> Maybe Int


或者你可以使用Maybe是一个函子的事实。事实上,它几乎是每个特定函子类的示例:FunctorApplicativeAlternativeFoldableTraversableMonadMonadPlus。它也提升半群到monoids

Dᴏɴ'ᴛ Pᴀɴɪᴄ现在

你不需要知道这些东西到底是什么但是,当你了解了它们的作用之后,你将能够编写非常简洁的代码,总是以正确的方式自动处理丢失的值,并且没有丢失检查的风险。
†因为Haskell是懒惰的,你通常不需要推迟任何计算。编译器将自动确保计算 * 在必要时 * 完成,而且不会更早。

oxosxuxt

oxosxuxt2#

Haskell中没有null。你想要的是Maybe单子。

data Maybe a
    = Just a
    | Nothing

字符串
Nothing表示经典的null,Just包含一个值。
然后你可以对它进行模式匹配:

foo Nothing  = Nothing
foo (Just a) = Just (a * 10)


或者使用case语法:

let m = Just 10
 in case m of
      Just v  -> print v
      Nothing -> putStrLn "Sorry, there's no value. :("


或者使用FunctorApplicativeAlternativeMonadMonadPlusFoldable的类型类示例提供的支持功能。
这可能看起来像这样:

foo :: Maybe Int -> Maybe Int -> Maybe Int
foo x y = do
  a <- x
  b <- y
  return $ a + b


您甚至可以使用更通用的签名:

foo :: (Monad m, Num a) => m a -> m a -> m a


这使得该函数适用于任何能够实现Monad所提供功能的数据类型。因此,您可以将foo(Num a) => Maybe a(Num a) => [a](Num a) => Either e a等一起使用。

am46iovg

am46iovg3#

Haskell没有null。这是一个设计特征。它完全防止了由于空指针异常而导致代码崩溃的任何可能性。
如果你看一下用命令式语言编写的代码,99%的代码 * 期望 * 的东西永远不会为null,如果你给予它null,它会发生灾难性的故障。但是1%的代码 * 确实 * 期望空值,并且 * 使用 * 这个特性来指定可选参数或其他东西。但是,通过查看代码,您无法轻松地判断哪些部分需要null作为法律的参数,哪些部分不需要。希望它的文件-但不要屏住呼吸!
在Haskell中,没有null。如果这个参数被声明为Customer,那么这里 * 必须 * 是一个实际的、真实的Customer。你不能只是传入一个null(故意或错误地)。因此,99%的代码期望一个真实的的Customer将始终工作。
那剩下的1%呢?我们有Maybe。但这是一个明确的事情;你必须明确地说“这个值是可选的”。而且你必须在使用它的时候明确地检查。你不能“忘记”检查;编译不了
所以,是的,没有“null”,但有Maybe,它有点类似,但更安全。

g0czyy6m

g0czyy6m4#

在Haskell(或许多其他FP语言)中不存在。如果你有一些T类型的表达式,它的求值将给予一个T类型的值,但有以下例外:

  • 无限递归可能会使程序“永远循环”,并且无法返回任何内容
let f n = f (n+1) in f 0

字符串

  • 运行时错误可以提前中止程序,例如:
  • 除零、负数的平方根和其他数值错误
  • head []fromJust Nothing和其他用于无效输入的部分函数
  • 显式调用undefinederror "message"或其他异常抛出原语

请注意,即使上述情况可能被视为称为“bottoms”(该名称来自域理论)的“特殊”值,通常也不能在运行时对这些值进行测试。因此,这些与Java的null完全不同。更准确地说,你不能写这样的东西

-- assume f :: Int -> Int
if (f 5) is a division-by-zero or infinite recursion
then 12
else 4


一些异常值可以在IO monad中捕获,但请忘记这一点--Haskell中的异常不是惯用的,而且大致上只用于IO错误。
如果您想要一个可以在运行时测试的异常值,请使用Maybe a类型,正如@bash0r已经建议的那样。这种类型类似于Scala的Option[A]或Java的不常用的Optional<A>
该值同时具有类型T和类型Maybe T,以便能够精确地识别哪些函数总是成功,哪些函数可能失败。例如,在Haskell中,以下内容是不受欢迎的:

-- Finds a value in a list. Returns -1 if not present.
 findIndex :: Eq a => [a] -> a -> Int


相反,这是优选的:

-- Finds a value in a list. Returns Nothing if not present.
 findIndex :: Eq a => [a] -> a -> Maybe Int


后者的结果不如前者方便,因为Int必须在每次调用时展开。这很好,因为通过这种方式,可以防止函数的每个用户简单地“忽略”不存在的情况,并编写错误代码。

相关问题