【问题标题】:How to get the value of a Maybe in Haskell如何在 Haskell 中获取 Maybe 的值
【发布时间】:2019-05-16 15:22:39
【问题描述】:

我对 Haskell 比较陌生,开始阅读“Real World Haskell”。

我刚刚偶然发现了 Maybe 类型,并有一个关于如何从 Just 1 接收实际值的问题。

我写了以下代码:

combine a b c = (eliminate a, eliminate b, eliminate c)
                where eliminate (Just a) = a
                      eliminate Nothing = 0

如果我使用,这很好用:

combine (Just 1) Nothing (Just 2)

但是,例如,如果我将 1 更改为字符串,它将不起作用。

我想我知道为什么:因为eliminate 必须返回一种类型,在这种情况下,是Int。但是如何更改 eliminate 以至少处理字符串(或者可能处理所有类型的类型)?

【问题讨论】:

  • 如果您希望回复字符串,Nothing 的响应应该是什么?
  • 您正在寻找一个可用于表示所有类型的“无结果”的令牌......这正是Nothing 的含义。没有完整的函数Maybe a -> a,因为没有统一的方法将Nothing 转换为a。你能做的最好的就是写一个函数(Maybe a, Maybe b, Maybe c) -> Maybe (a, b, c)
  • 正如我用我荒谬的长回答所证明的那样,这个问题与 TomMD 链接的问题不同(在那个问题中,发帖人说输入永远不会是 Nothing,在这个问题上,Nothing 是明确的可能性,实际上是类型问题的根源)
  • 如果您告诉我们您真正想要做什么,也许会有所帮助。您可能想要“fromMaybe”函数,或者您可能想要利用 Maybe 是一个 monad 的事实。

标签: return-value haskell maybe


【解决方案1】:

来自标准Prelude

maybe :: b -> (a -> b) -> Maybe a -> b
maybe n _ Nothing = n
maybe _ f (Just x) = f x

给定一个默认值和一个函数,将该函数应用于Maybe中的值或返回默认值。

您的eliminate 可以写成maybe 0 id,例如应用恒等函数,或者返回 0。

来自标准的Data.Maybe

fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x

这是一个 partial 函数(与 total 函数相反,它不会为每个输入返回一个值),但会在可能的情况下提取该值。

【讨论】:

  • 也可以用maybe代替Data.Maybe.fromMaybe :: a -> Maybe a -> a
  • @John 不错。在我的回答中,你可以写eliminate = fromMaybe nada
【解决方案2】:

[从作者编辑,6 年后] 这是一个不必要的冗长答案,我不确定它为什么被接受。按照最高投票答案中的建议使用maybeData.Maybe.fromMaybe。接下来的内容更多的是一个思想实验,而不是实际的建议。

因此,您正在尝试创建一个适用于多种不同类型的函数。这是上课的好时机。如果您使用 Java 或 C++ 编程,Haskell 中的类有点像这些语言中的接口。

class Nothingish a where
    nada :: a

该类定义了一个值nada,它应该是该类的Nothing 等价物。现在是有趣的部分:制作这个类的实例!

instance Nothingish (Maybe a) where
    nada = Nothing

对于Maybe a 类型的值,Nothing-like 值是Nothing!这将是一个奇怪的例子。但在此之前,让列表也成为此类的一个实例。

instance Nothingish [a] where
    nada = []

空列表有点像 Nothing,对吧?所以对于一个字符串(这是一个字符列表),它会返回空字符串,""

数字也是一个简单的实现。您已经指出 0 显然代表数字的“虚无”。

instance (Num a) => Nothingish a where
    nada = 0

除非您在文件顶部放置一个特殊行,否则这个实际上不会起作用

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}

或者当你编译它时,你可以为这些语言编译指示设置标志。不用担心它们,它们只是让更多东西发挥作用的魔法。

所以现在你已经有了这个类和它的这些实例......现在让我们重新编写你的函数来使用它们!

eliminate :: (Nothingish a) => Maybe a -> a
eliminate (Just a) = a
eliminate Nothing  = nada

注意我只把0改成了nada,其余的都是一样的。让我们试一试吧!

ghci> eliminate (Just 2)
2
ghci> eliminate (Just "foo")
"foo"
ghci> eliminate (Just (Just 3))
Just 3
ghci> eliminate (Just Nothing)
Nothing
ghci> :t eliminate
eliminate :: (Nothingish t) => Maybe t -> t
ghci> eliminate Nothing
error! blah blah blah...**Ambiguous type variable**

看起来很适合价值观和东西。注意 (Just Nothing) 变成 Nothing,明白吗?那是一个奇怪的例子,一个Maybe in a Maybe。无论如何...eliminate Nothing 呢?好吧,结果类型是模棱两可的。它不知道我们在期待什么。所以我们必须告诉它我们想要什么类型。

ghci> eliminate Nothing :: Int
0

继续尝试其他类型;你会看到它每一个都得到nada。所以现在,当您将此函数与 combine 函数一起使用时,您会得到:

ghci> let combine a b c = (eliminate a, eliminate b, eliminate c)
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
(2,"foo",Just 3)
ghci> combine (Just 2) Nothing (Just 4)
error! blah blah Ambiguous Type blah blah

请注意,您仍然必须指明您的“Nothing”是什么类型,或者指明您期望的返回类型。

ghci> combine (Just 2) (Nothing :: Maybe Int) (Just 4)
(2,0,4)
ghci> combine (Just 2) Nothing (Just 4) :: (Int, Int, Int)
(2,0,4)

或者,您可以通过将函数的类型签名明确地放在源代码中来限制函数允许的类型。如果该函数的逻辑用途是仅与相同类型的参数一起使用,则这是有道理的。

combine :: (Nothingish a) => Maybe a -> Maybe a -> Maybe a -> (a,a,a)
combine a b c = (eliminate a, eliminate b, eliminate c)

现在它只有在所有三个 Maybe 都是相同类型的情况下才有效。这样,它会推断出Nothing 与其他的类型相同。

ghci> combine (Just 2) Nothing (Just 4)
(2,0,4)

没有歧义,耶!但是现在混搭是错误的,就像我们之前做的那样。

ghci> combine (Just 2) (Just "foo") (Just (Just 3))
error! blah blah  Couldn't match expected type  blah blah
blah blah blah    against inferred type         blah blah

嗯,我认为这是一个足够长且夸大其词的答案。享受吧。

【讨论】:

  • 另请注意:新函数仅适用于包装在 Maybe 中的 Nothinglike 类型的实例。但是,如果一个类型不是Nothinglike,那么在给定Nothing 的输入时,为什么你会期望这个函数产生那个类型的值呢? ;)
  • 哇,对于一个新手来说,这有很多新东西;)如果我理解你的解决方案以及学习这门语言,如果解决这么一个小问题就这么难,我真的会产生一些严重的疑问。但是,是的,我会慢慢来,做一些例子并决定它是否适合我;)(最后有一个小问题,如果消除只适用于字符串和整数会更容易,并且没有任何结果应该导致“”还是 0?)
  • @Moe 麻烦在于,在 Haskell 中,很难创建一种适用于多种类型但并非适用于所有类型的方法。所以诀窍是让它适用于某些类型的类型。继续阅读 RWH,你很快就会明白所有这些内容。
  • 这几乎可以肯定是错误的答案,过于复杂,并且方式来解决这样一个初学者问题。请不要教新手,简单的事情在 Haskell 中是非常困难的!
  • Dan,如果您有时间(我不一定希望您这样做),请用您来之不易的智慧更新您的答案,使其更具凝聚力。
【解决方案3】:

我也是 Haskell 的新手,所以我不知道它是否存在于平台中(我确定确实存在),但是如果存在,“get or else”函数如何获取值, 否则返回默认值?

getOrElse::Maybe a -> a -> a
getOrElse (Just v) d = v
getOrElse Nothing d  = d
【解决方案4】:

这是我遇到这个问题时一直在寻找的答案:

https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html#v:fromJust

...同样,对于Either

https://hackage.haskell.org/package/either-unwrap-1.1/docs/Data-Either-Unwrap.html

它们提供了我自己编写的函数,可以从上下文中解开值。

【讨论】:

    【解决方案5】:

    eliminate 函数的类型签名是:

    eliminate :: Maybe Int -> Int
    

    这是因为它在 Nothing 上返回 0,迫使编译器在您的 eliminate 函数中假定 a :: Int。因此,编译器推断combine 函数的类型签名为:

    combine :: Maybe Int -> Maybe Int -> Maybe Int -> (Int, Int, Int)
    

    这正是为什么当您将字符串传递给它时它不起作用的原因。

    如果你写成:

    combine a b c = (eliminate a, eliminate b, eliminate c)
                    where eliminate (Just a) = a
                          eliminate Nothing = undefined
    

    那么它可以与 String 或任何其他类型一起使用。原因在于undefined :: a,这使得eliminate 具有多态性并且适用于Int 以外的类型。

    当然,这不是您的代码的目的,即使 combine 函数完全。

    确实,即使将 combine 应用于某些 Nothing 参数会成功(这是因为 Haskell 默认是惰性的),一旦您尝试评估结果,您就会得到一个运行时错误,因为 undefined can'不会被评估为有用的东西(简单地说)。

    【讨论】:

    • @Christiano 感谢您的回答。正如我在我的问题中提到的,我已经想到,强制编译器假定返回类型为整数可能为 0。但这对我的问题没有帮助。如何在 haskell 中编写一个函数,它接受 3 个 Maybe 类型,并返回一个元组,其中的值是 Maybe 的值,如果什么都不是,则为 0?
    • 如果您打算在 Maybes 中传递 Int 以外的值,则不能。我知道这可能看起来令人沮丧,但想想看:它会有用吗?你能想象一个函数消耗了 combine 的结果吗?它可能有什么签名?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-20
    • 2022-01-12
    • 2017-12-02
    • 2014-07-13
    • 2011-01-24
    • 1970-01-01
    相关资源
    最近更新 更多