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

时间:2018-02-26 16:12:11

标签: haskell pattern-matching semantics language-implementation

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

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

4 个答案:

答案 0 :(得分:15)

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

在数学上,构造函数是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,其:<|:|>模式同义词。

答案 1 :(得分:12)

这是一个值得考虑的问题,到目前为止它已经得到了明智的答案(只允许施工人员咕噜咕噜,嘀咕咕噜咕噜,模糊不清),但还有时间改变这一切。

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

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

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

它不明确的反对意见是合法的,但不是交易破坏者。像++这样的Plugger-togetherers只提供有限输入的有限多次分解(如果你正在研究无限数据,这是你自己的了望),所以最糟糕的是它涉及的是什么搜索,而不是 magic (发明任意函数可能抛弃的任意输入)。搜索调用某些优先级的方法,但我们的有序匹配规则也是如此。搜索也可能导致失败,但同样可以匹配。

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

编辑>当然,我应该补充一点,如果你在一个模式中有多个有弹性的东西,你真的需要搜索,所以建议的fail模式不应该&#39 ; t触发任何选择。当然,找到列表的结尾需要时间。)

想象一下,编写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|) 只是列表的子列表示例(这是令人困惑的,因为列表与一个空洞的子列表看起来很像一个列表,但同样的情况并非如此其他数据类型)。

非常有趣的是,有一个++点,我们甚至可以通过它们的洞和根来保持多孔数据,然后在恒定的时间内破坏性地插入。只有当你没有注意到你正在做这件事时,这是一种耻辱行为。

答案 2 :(得分:9)

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

数据构造函数是持久的;像'c':[]这样的值无法进一步简化,因为 [Char]类型的基本值。但是,"c" ++ "d"之类的表达式可以随时替换为等效的"cd",因此无法可靠地计算出来用于模式匹配。

(您可能会认为"cd"始终可以替换为"c" ++ "d",但通常情况下,列表与通过++的分解之间不存在一对一的映射关系。 "cde"是否等同于"c" ++ "de""cd" ++ "e"用于模式匹配?)

答案 3 :(得分:6)

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

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