Haskell:为啥模式匹配中不允许使用 ++?

Posted

技术标签:

【中文标题】Haskell:为啥模式匹配中不允许使用 ++?【英文标题】:Haskell: Why ++ is not allowed in pattern matching?Haskell:为什么模式匹配中不允许使用 ++? 【发布时间】:2018-08-06 03:35:30 【问题描述】:

假设我们想在 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

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

【问题讨论】:

如果你没看过,你可能会喜欢prolog Curry 是一种简洁的小语言,这种事情是可能的。它是一种关系型编程语言,这意味着它的基本组件是xs == ys ++ [y] 之类的关系,而不是case xs of y:ys -> undefined; [] -> undefined 之类的函数 【参考方案1】:

++ 不是构造函数,它只是一个普通函数。你只能匹配构造函数。

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

【讨论】:

ViewPatternsPatternSynonyms @luqui 谢谢。不知道我在想哪个。自从我写 Haskell 以来已经有一段时间了。我稍后会编辑这些链接。【参考方案2】:

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

数据构造函数是持久的;像'c':[] 这样的值不能进一步简化,因为它[Char] 类型的基本值。但是,像 "c" ++ "d" 这样的表达式可以随时替换为其等效的 "cd",因此不能可靠地指望它出现在模式匹配中。

(您可能会争辩说"cd" 总是可以替换为"c" ++ "d",但一般来说,列表和通过++ 进行的分解之间不存在一对一的映射关系。"cde" 是否等同于"c" ++ "de""cd" ++ "e" 用于模式匹配?)

【讨论】:

【参考方案3】:

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

在数学上,构造函数是一个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

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

允许在左右端高效进行模式匹配的容器是Data.Sequence,它的:&lt;|:|&gt; 模式同义词。

【讨论】:

Op 的模式(xs++[x]) 是明确的(至少我直观地理解它的含义),它也与“N Plus K 模式”相同(当然+ 不是单射的) . 是的,这与 N Plus K Patterns 的想法相同,由于在很大程度上归结为非注入性的问题,它已从语言中清除。可以使 sum' (xs++[x]) = x + sum' xsfact (x+1) = (x+1) * fact x 等特殊情况起作用,但它不能正确概括。 对,但我真的很想了解究竟是什么将像 op 的模式(实际上是单射的)与模棱两可/无效的模式区分开来。我认为我们可以根据具体情况使用归纳法来证明它,但我真的不知道如何对这个问题进行推理【参考方案4】:

这是一个值得思考的问题,到目前为止,它已经得到了合理的答案(mutter 只允许构造函数、mutter 注入性、mutter 歧义),但仍有时间改变这一切。

我们可以说出规则是什么,但大多数关于规则为何如此的解释都是从过度概括问题开始的,解决了为什么我们不能对任何旧函数进行模式匹配(mutter Prolog)。这是为了忽略 ++ 不是任何旧函数这一事实:它是一个(空间上)linear 插入东西的函数,由列表的拉链结构引起。模式匹配是关于把东西拆开,实际上,用插件和模式变量表示组件的过程。它的动机是清晰。所以我愿意

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

这不仅是因为它让我想起了 30 年前我实现一种功能语言时的乐趣,而这种语言的模式匹配正好提供了这种功能。

认为它模棱两可的反对意见是合理的,但不是破坏交易的理由。像++ 这样的插件组合只提供有限输入的有限多次分解(如果您正在处理无限数据,那是您自己的观察),所以最坏的情况是搜索,而不是magic(发明任意函数可能丢弃的任意输入)。搜索需要一些优先排序方式,但我们的有序匹配规则也是如此。搜索也可能导致失败,但同样可以匹配。

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

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

想象一下写Alternative 计算有某种有趣的括号,例如,(|) 表示empty(|a1|a2|) 表示(|a1|) &lt;|&gt; (|a2|),而一个普通的旧(|f s1 .. sn|) 表示pure f &lt;*&gt; s1 .. &lt;*&gt; sn。也可以想象(|case a of p1 -&gt; a1; .. pn-&gt;an|) 根据Alternative 组合子对搜索模式(例如涉及++)进行合理的翻译。我们可以写

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

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

有趣的是,有了LinearTypes 的位置,我们甚至可以通过漏洞和根来保存漏洞数据,然后在恒定时间内破坏性地堵塞数据。只有当您没有注意到自己在做这件事时,这才是可耻的行为。

【讨论】:

lookup k [..., (k,v), ...] = Just v 怎么样?我们还可以有[x, ...][x, ...xs...][_, ...xs...] 等。作为构造函数也可以,比如sieve [p, ...xs...] = [x, ...sieve [x | x &lt;- xs, rem x p &gt; 0]...]。或(++) a b = [...a..., ...b...]。一个优点是,读者不必猜测::: (ML)、++ 等的含义 - 意图是显而易见的。希望。顺便说一句,我最近偶然发现了en.wikipedia.org/wiki/Refal,这似乎是相关的(1960 年代苏联的一种模式匹配术语重写语言)。 我在 80 年代后期所做的事情实际上是从 SNOBOL 中窃取了它的符号:它是一个模式匹配方括号 LISP,但以 $ 开头的变量代表列表段,所以 @987654353 @。但正如我在上面所说的,这不仅仅是关于列表,所以我认为我们需要超越。以列表为中心的符号可以导致以列表为中心的思维。 出于好奇,如果麦卡锡的原始论文的 eval 带有所有辅助词,则此符号(加上一些列表理解)允许使用 very succinct - 13 LOC - reformulation。

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

如何用结构模式匹配表达 hasattr() 鸭子类型逻辑?

awk从入门到入土简单条件匹配

模板模式

模板模式

Java中,状态模式和策略模式的区别

正则表达式