Haskell 如何处理 Maybe 列表

5
我是一个新手,对Haskell不太熟悉,也不知道如何使用Maybe [a]。通常我使用OOP(VB.NET)进行编码,在空闲时间我想学习Haskell(别问为什么 ;) )。
那么,我想做什么呢?我想读取两个具有数字ID的文件,并仅查找两个文件中匹配的ID。读取文件不是一件大事,它非常简单易行。现在,我得到了两个可能为 [Ids] (对于一个简单的例子,只需将IDs视为Int)的列表。所以我需要的函数看起来像这样。
playWithMaybe :: (Maybe [a]) -> (Maybe [Int]) -> [Int]

现在我想像以前一样访问列表成员

playWithMaybe (x:xs) (y:ys) = undefined

但是不幸的是,GHC说这两个列表都不允许这样做。

Couldn't match expected typeMaybe [Int]’ with actual type ‘[t0]’

我尝试过一些方法,但无法访问列表成员。有人可以帮我吗?简单易懂的解释会是一个很好的帮助!


2
也许 [a] 不是一个列表类型,它是一个 Maybe 类型,可能(或可能不)包含一个列表。—— 我不清楚你试图做什么。 - leftaroundabout
就像列表有两个基本的构造函数([]x : xs),Maybe也有两个基本的构造函数(NothingJust x),你可以对其进行模式匹配。 - melpomene
不是很清楚为什么你需要Maybe,以及你将如何处理Maybe [a](你可以做的事情不多)。也许需要更多上下文。 - n. m.
@melpomene:返回一个没有元素的列表。也许你可以给我一个例子,如何正确匹配模式以便使用这个列表?比较两个列表的值,只返回两个列表中都有的值,例如:<code>[1,2,3] [2,3,4] -> [2,3] - sandkasten
要将 Maybe [a] 转换为 [a],您可以使用以下函数:foo Nothing = []; foo (Just xs) = xs。或者导入 Data.Maybe 并直接使用 fromMaybe [] - melpomene
显示剩余3条评论
3个回答

9
为了从不同的方向解决您的问题,我认为您不需要一个处理两个 Maybe [a] 的函数。请耐心听我解释:
基本上,您想要执行的操作是对两个列表进行操作以生成一个新的列表,例如:
yourFunction :: [a] -> [a] -> [a]
yourFunction a b = ...

没问题,您可以并且应该像这样编写yourFunction。事实上,您拥有的数据是Maybe [a],它捕获了一些附加的辅助信息:创建输入列表的操作可能已经失败。下一步是将yourFunction与辅助信息链接在一起。这正是do符号的目的,将纯操作(如yourFunction)与上下文(即创建您的输入列表之一可能已失败的事实)混合在一起:

playWithMaybe :: Maybe [a] -> Maybe [a] -> Maybe [a]
playWithMaybe maybeA maybeB =
  do a <- maybeA   -- if A is something, get it; otherwise, pass through Nothing
     b <- maybeB   -- if B is something, get it; otherwise, pass through Nothing
     Just (yourFunction a b)  -- both inputs succeeded!  do the operation, and return the result

但是,事实证明您可能需要使用其他类型的上下文进行工作(例如,不使用Maybe简单地捕获“发生了一些糟糕的事情”,我们可以使用Either来捕获“发生了一些糟糕的事情,并且这是对所发生事情的描述)。回顾一下playWithMaybe函数,"Maybe-ness"只出现在最后一行的Just中。原来Haskell提供了一个通用函数pure来将纯值(例如从yourFunction获得的值)包装为最小的上下文:

playWithMaybe' :: Maybe [a] -> Maybe [a] -> Maybe [a]
playWithMaybe' maybeA maybeB =
  do a <- maybeA
     b <- maybeB
     pure (yourFunction a b)

然而,Haskell还有一种通用类型来抽象上下文的概念,即Monad。这使得我们的函数变得更加通用:

playWithMonad :: Monad m => m [a] -> m [a] -> m [a]
playWithMonad mA mB =
  do a <- mA
     b <- mB
     pure (yourFunction a b)

现在我们有一个非常通用的东西,事实证明它非常通用,它已经在标准库中了!(这变得相当微妙,所以如果还不太明白也不用担心。)

import Control.Applicative
play :: Monad m => m [a] -> m [a] -> m [a]
play mA mB = liftA2 yourFunction mA mB

甚至可以
import Control.Applicative
play' :: Monad m => m [a] -> m [a] -> m [a]
play' = liftA2 yourFunction

我为什么突然从Monad转向Applicative? Applicative与Monad类似,但更加通用,因此如果可以选择,通常最好使用Applicative(就像之前选择使用pure而不是return一样)。为了更完整的解释,强烈推荐阅读《学习 Haskell》(http://learnyouahaskell.com/chapters)的第11和12章。注意-一定要先阅读第11章!只有在掌握Functor和Applicative后,Monad才有意义。


1
我非常喜欢阅读你的答案,它提供了一个很好的例子和解释。我认为你在最后提到的是我的练习将要达到的目标 :) 也许我太过于沉迷于面向对象编程,总是检查某些东西是否为空,这样或那样... - sandkasten
@sandkasten 这也检查了列表是否成功生成(Just xs)或者它们中至少有一个失败(Nothing)。在 Haskell 中,通过使用 liftA2,所有这些都会自动发生,这使得代码非常清晰和简洁。 - ThreeFx

5

总的来说:

yourFunction Nothing Nothing = ...
yourFunction (Just xs) Nothing = 
  case xs of
    [] -> ...
    x':xs' -> ...
-- or separately: 
yourFunction (Just []) Nothing = ... 
yourFunction (Just (x:xs)) Nothing = ...

等等。需要单独处理的情况取决于具体的功能。更可能的是,您将结合在Maybe上工作的函数与在[]上工作的函数。

如果要为Nothing“仅返回一个没有元素的列表”,则可以编写

maybeToList1 :: Maybe [a] -> [a]
maybeToList1 Nothing = []
maybeToList1 (Just xs) = xs

更好的编写同样功能的方法是 maybeToList1 = maybe [] id (maybe 的文档) 或者 maybeToList1 = fromMaybe [],但由于你刚开始学习,也许你可以稍后再回来参考这个问题。

很不幸的是,我只能将一个帖子作为答案检查,但我也喜欢这个。它与我的面向对象编程逻辑很接近,首先要检查某个东西是否包含有效值,然后再开始处理它。 - sandkasten

0

像其他人所说的,[Int]Maybe [Int]不是同一回事。Maybe [Int]包含了额外的信息,即列表可能存在也可能不存在。你说你从文件中读取了Ids。也许,Maybe表示文件是否存在,而空列表表示文件存在但不包含Ids。

如果你想要处理这个列表,你首先需要定义如果没有列表该怎么做。你可以使用这个函数来提取列表:

fromMaybe :: a -> Maybe a -> a

也许您希望把没有列表视为空列表:
fromMaybe [] :: Maybe [a] -> [a]

也许你想要使整个程序崩溃:
fromMaybe (error "Missing list") :: Maybe a -> a

还有更一般的maybe函数,如果默认值与Maybe中包含的类型不同,您可以使用它:

maybe :: b -> (a -> b) -> Maybe a -> b

这两个函数都定义在模块Data.Maybe中。

你也可以像它们已经存在一样直接处理列表,并通过使用Applicative稍后再担心它们的存在。你说你想找到两个列表共同的Id。你可以这样做:

maybeCommonIds :: Maybe [Int] -> Maybe [Int] -> Maybe [Int]
maybeCommonIds xs ys = intersect <$> xs <*> ys

intersectData.List 中定义。使用 maybeCommonIds 将导致 Maybe [Int]。其中包含的列表将保存公共 ID,但如果两个列表中任何一个不存在,则没有公共 ID 列表。在您的情况下,这可能与没有公共 ID 相同。在这种情况下,您可能希望将结果传递给 fromMaybe [],以获取列表。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接