Haskell:为什么++不允许模式匹配?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Haskell:为什么++不允许模式匹配?相关的知识,希望对你有一定的参考价值。

假设我们想在Haskell中编写自己的sum函数:

sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (x:xs) = x + sum' xs

为什么我们不能这样做:

sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (xs++[x]) = x + sum' xs

换句话说,为什么我们不能在模式匹配中使用++

答案

您只能在构造函数上进行模式匹配,而不能在一般函数上进

在数学上,构造函数是一个injective函数:每个参数组合给出一个唯一值,在本例中是一个列表。因为该值是唯一的,所以该语言可以将其再次解构为原始参数。即,当您在:上模式匹配时,您基本上使用该函数

uncons :: [a] -> Maybe (a, [a])

检查列表是否是您可以用:构建的形式(即,如果它是非空的),如果是,则返回头部和尾部。

例如,++不是单射的

Prelude> [0,1] ++ [2]
[0,1,2]
Prelude> [0] ++ [1,2]
[0,1,2]

这些表示都不是正确的,那么如何重新解构列表呢?

然而,你可以做的是定义一个新的“虚拟”构造函数,其作用类似于:,因为它总是从列表的其余部分中分离出一个元素(如果可能的话),但是在右边这样做:

{-# LANGUAGE PatternSynonyms, ViewPatterns #-}

pattern (:>) :: [a] -> a -> [a]
pattern (xs:>ω) <- (unsnoc -> Just (xs,ω))
 where xs:>ω = xs ++ [ω]

unsnoc :: [a] -> Maybe ([a], a)
unsnoc [] = Nothing
unsnoc [x] = Just x
unsnoc (_:xs) = unsnoc xs

然后

sum' :: Num a => [a] -> a
sum' (xs:>x) = x + sum xs
sum' [] = 0

请注意,这是非常低效的,因为:>模式同义词实际上需要挖掘整个列表,因此sum'具有二次而非线性复杂性。

允许有效地在左端和右端进行模式匹配的容器是Data.Sequence,其:<|:|>模式同义词。

另一答案

这是一个值得考虑的问题,到目前为止它已经得到了明智的答案(只允许构造函数,嘀咕的注入,笨拙的模糊),但仍有时间改变这一切。

我们可以说规则是什么,但大多数解释为什么规则是他们从过度推广问题开始,解决为什么我们不能模式匹配任何旧函数(mutter Prolog)。这是为了忽略++不是任何旧函数的事实:它是一个(空间)线性插入 - 填充功能,由列表的拉链结构引起。模式匹配就是将事物分开,实际上,根据插件 - 注意器和模块变量来表示组件。它的动机是清晰。所以我想

lookup :: Eq k => k -> [(k, v)] -> Maybe v
lookup k (_ ++ [(k, v)] ++ _) = Just v
lookup _ _                    = Nothing

而且这不仅仅是因为它让我想起三十年前我实现了一种功能语言的乐趣,而这种功能语言的模式匹配就是这样。

它含糊不清的异议是合法的,但不是破坏者。像++这样的Plugger-togetherers只提供有限输入的有限多次分解(如果你正在处理无限数据,这是你自己的了望),那么所涉及的是最糟糕的搜索,而不是魔法(发明任意函数可能具有的任意输入)扔掉了)。搜索调用某些优先级的方法,但我们的有序匹配规则也是如此。搜索也可能导致失败,但同样可以匹配。

我们有一种合理的方法来管理通过Alternative抽象提供替代方案(失败和选择)的计算,但我们不习惯将模式匹配视为这种计算的一种形式,这就是我们仅在表达式语言中利用Alternative结构的原因。高贵的,如果是不切实际的例外是do-notation中的匹配失败,它称为相关的fail而不是必然崩溃。模式匹配是尝试计算适合评估“右侧”表达的环境;无法计算这样的环境已经处理好了,为什么不选择呢?

(编辑:当然,我应该补充一点,如果你在一个模式中有多个弹性的东西,你真的需要搜索,所以建议的xs++[x]模式不应该触发任何选择。当然,找到结束需要时间列表。)

想象一下,有一些有趣的括号用于编写Alternative计算,例如,(|)意思是empty(|a1|a2|)意思是(|a1|) <|> (|a2|),而常规的旧(|f s1 .. sn|)意思是pure f <*> s1 .. <*> sn。人们可能还可以想象(|case a of {p1 -> a1; .. pn->an}|)++组合器进行搜索模式的合理翻译(例如涉及Alternative)。我们可以写

lookup :: (Eq k, Alternative a) => k -> [(k, v)] -> a k
lookup k xs = (|case xs of _ ++ [(k, v)] ++ _ -> pure v|)

对于由可微函子的固定点生成的任何数据类型,我们可以获得一种合理的搜索模式语言:符号微分正是将结构元组转换为可能的子结构选择的原因。好的旧++只是列表的子列表示例(这是令人困惑的,因为列表与一个孔的子列表看起来很像一个列表,但其他数据类型不一样)。

有趣的是,有一个LinearTypes点,我们甚至可以通过他们的洞和根来保持空洞的数据,然后在恒定的时间内破坏性地插入。只有你没注意到你这样做,这才是可耻的行为。

另一答案

您只能对数据构造函数进行模式匹配,而++是一个函数,而不是数据构造函数。

数据构造函数是持久的;像'c':[]这样的值不能进一步简化,因为它是[Char]类型的基本值。然而,像"c" ++ "d"这样的表达式可以在任何时候用它的等价"cd"替换,因此不能可靠地指望它存在用于模式匹配。

(你可能会认为"cd"总是可以用"c" ++ "d"取代,但一般来说,列表和++分解之间没有一对一的映射."cde"是否等同于"c" ++ "de""cd" ++ "e"用于模式匹配?)

另一答案

++不是构造函数,它只是一个简单的函数。您只能在构造函数上匹配。

您可以使用ViewPatternsPatternSynonyms来增强模式匹配的能力(感谢@luqui)。

以上是关于Haskell:为什么++不允许模式匹配?的主要内容,如果未能解决你的问题,请参考以下文章

Haskell 模式匹配 - 它是啥?

Haskell:绑定模式匹配的地方

函数返回与函数参数中的 Haskell 模式匹配

如何在haskell中对两个参数进行模式匹配

如何在Haskell中与代数类型进行模式匹配

Haskell代码编程