Haskell 使用 Maybe 列表

Haskell working with Maybe lists

我是 haskell 的新手,不知道如何使用 Maybe [a]。通常我在编写 OOP (VB.NET),在空闲时间我想学习 haskell(不要问为什么 ;))。

那么,我想做什么?我想读取两个具有数字 ID 的文件,并仅找出两个文件中匹配的 ID。读取文件不是什么大事,它工作起来非常简单。现在,我得到两个 Maybe[Ids] 的列表(举个简单的例子,假设 ID 是 Int)。所以我需要的功能看起来像这样

playWithMaybe :: (Maybe [a]) -> (Maybe [Int]) -> [Int]

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

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

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

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

所以我试了一下,但没有找到访问列表成员的方法。有人可以帮我吗?稍微解释一下就好了!

总的来说:

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

等等。哪些情况需要单独处理,要看具体的函数。您更有可能将处理 Maybe 的函数与处理 [].

的函数结合起来

如果你想"Just return an list with no elements"换成Nothing,那么你可以写

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

编写相同函数的更好方法是 maybeToList1 = maybe [] id (docs for maybe) 或 maybeToList1 = fromMaybe [],但由于您刚刚开始,您可能希望稍后再回到这一点。

为了从不同的方向解决您的问题,我认为您 想要一个处理两个 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

但事实证明,您可能还想使用其他类型的上下文(一个简单的上下文,而不是仅捕获 "something bad happened" 的 Maybe,我们可以使用 Either 来捕获 "something bad happened, and here is a description of what happened). Looking back at playWithMaybe, the "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)。为了获得更完整的解释,我强烈推荐 Learn You a Haskell (http://learnyouahaskell.com/chapters),特别是第 11 章和第 12 章。注意 - 一定要先阅读第 11 章!只有掌握了 Functor 和 Applicative 之后,Monad 才有意义。

正如其他人所说,[Int]Maybe [Int] 不是一回事。 Maybe [Int] 包括列表可能存在或不存在的额外信息。你说你从文件中读取了 ID。也许,Maybe 表示文件是否存在,而空列表表示文件确实存在但不包含任何 ID。

如果要使用列表,首先需要定义没有列表时要执行的操作。您可以使用此函数提取列表:

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 [],以取回列表。