当模式匹配失败时,为什么Haskell列表推导不会导致错误?

时间:2009-03-16 04:34:11

标签: haskell pattern-matching list-comprehension

我试图理解Haskell列表理解在模式匹配方面是如何“在幕后”工作的。以下ghci输出说明了我的观点:

Prelude> let myList = [Just 1, Just 2, Nothing, Just 3]
Prelude> let xs = [x | Just x <- myList]
Prelude> xs
[1,2,3]
Prelude>

如您所见,它可以跳过“Nothing”并仅选择“Just”值。我理解List是一个monad,定义为(源自Real World Haskell,第14章):

instance Monad [] where
    return x = [x]
    xs >>= f = concat (map f xs)
    xs >> f = concat (map (\_ -> f) xs)
    fail _ = []

因此,列表推导基本上为列表推导中选择的每个元素构建一个单例列表并将它们连接起来。如果模式匹配在某个步骤失败,则使用“失败”功能的结果。换句话说,“Just x”模式不匹配,因此在调用“concat”之前,[]用作占位符。这就解释了为什么“Nothing”似乎被忽略了。

我不明白的是,Haskell如何知道调用“失败”功能?它是“编译魔术”,还是你可以在Haskell中自己编写的功能?是否可以编写以下“select”函数以与列表推导相同的方式工作?

select :: (a -> b) -> [a] -> [b]
select (Just x -> x) myList       -- how to prevent the lambda from raising an error?
[1,2,3]

3 个答案:

答案 0 :(得分:31)

虽然Haskell的实现可能不会在内部直接这样做,但以这种方式思考它会很有帮助:)

[x | Just x <- myList]

...变成:

do
    Just x <- myList
    return x

......这是:

myList >>= \(Just x) -> return x

关于你的问题:

  

我不明白的是,Haskell如何知道调用“失败”函数?

在do-notation中,如果模式绑定失败(即Just x),则调用fail方法。对于上面的例子,它看起来像这样:

myList >>= \temp -> case temp of
    (Just x) -> return x
    _        -> fail "..."

因此,每次在可能失败的monadic上下文中进行模式匹配时,Haskell都会插入对fail的调用。尝试使用IO:

main = do
    (1,x) <- return (0,2)
    print x -- x would be 2, but the pattern match fails

答案 1 :(得分:10)

rule for desugaring a list comprehension需要[ e | p <- l ]形式的表达式(其中e是表达式,p是模式,l列表表达式)表现像

let ok p = [e]
    ok _ = []
in concatMap ok l

Haskell的早期版本有monad comprehensions,这些版本已从语言中删除,因为它们难以阅读并且使用do符号表示多余。 (列表理解也是多余的,但它们并不难以阅读。)我认为[ e | p <- l ]作为 monad (或者,确切地说) ,作为 monad,零)会产生类似

的东西
let ok p = return e
    ok _ = mzero
in l >>= ok

其中mzero来自MonadPlus类。这非常接近

do { p <- l; return e }

desugars

let ok p = return e
    ok _ = fail "..."
in l >>= ok

当我们选择List Monad时,我们有

return e = [e]
mzero = fail _ = []
(>>=) = flip concatMap

即,3种方法(列表推导,monad comprehensions,do表达式)与列表等效。

答案 2 :(得分:6)

我不认为列表推导语法与列表([])或Maybe碰巧是{{1}的实例这一事实有很大关系。 }类型。

列表推导确实是编译器魔术语法糖,但这是可能的,因为编译器知道Monad数据类型的结构。

以下是列表理解编译的内容:(好吧,我认为,我实际上并未针对GHC进行检查)

[]

正如您所看到的,编译器不必调用xs = let f = \xs -> case xs of Just x -> [x] _ -> [] in concatMap f myList 函数,它可以简单地内联一个空列表,因为它知道列表


有趣的是,列表理解语法'跳过'模式匹配失败的事实在一些库中用于进行泛型编程。请参阅Uniplate library中的示例。


编辑哦,为了回答您的问题,您无法使用您提供的lambda调用fail函数。如果用select值调用它,它确实会在模式匹配失败时失败。

你可以从上面的代码中传递Nothing函数,但是f的类型会是:

select

完全没问题,你可以在内部使用select :: (a -> [b]) -> [a] -> [b] 函数: - )

此外,新的concatMap现在具有列表的monadic绑定运算符的类型(其参数被翻转):

select