因此,函数式语言中的模式匹配非常棒。我想知道为什么大多数命令式语言都没有实现这个功能?据我了解,Scala 是唯一具有模式匹配的“主流”命令式语言。案例/开关结构的功能就没有那么强大了。
我特别感兴趣的是,模式匹配的缺失是技术原因还是历史原因?
因此,函数式语言中的模式匹配非常棒。我想知道为什么大多数命令式语言都没有实现这个功能?据我了解,Scala 是唯一具有模式匹配的“主流”命令式语言。案例/开关结构的功能就没有那么强大了。
我特别感兴趣的是,模式匹配的缺失是技术原因还是历史原因?
这主要是历史性的。模式匹配——更重要的是,代数数据类型——是在 1980 年左右为函数式语言 Hope 发明的。从那里它很快进入了机器学习,后来被其他函数式语言如 Miranda 和 Haskell 采用。主流的命令式世界通常需要几十年的时间来接受新的编程语言思想。
特别阻碍采用的一个原因是主流长期以来一直由面向对象的意识形态主导。在那个世界里,任何不是由对象和子类型表达的东西在道德上都被认为是“错误的”。有人可能会争辩说,代数数据类型是一种对立面。
也许还有一些技术原因使它在函数式语言中更自然:
规则范围规则和变量的细粒度绑定构造是函数式语言的规范,但在主流命令式语言中不太常见。
尤其如此,因为模式绑定了不可变的变量。
类型检查模式匹配依赖于功能类型系统的结构和刚性,以及它们与计算逻辑的密切联系。主流类型系统通常与此相去甚远。
代数数据类型需要堆分配(除非您想浪费大量空间并禁止递归),并且如果没有垃圾收集会非常不方便。但是,主流语言中的 GC(如果存在)通常针对重量级对象进行优化,而不是针对轻量级函数数据进行优化。
直到最近(更准确地说:直到 Scala),人们认为模式匹配与表示无知(即OO的定义特征)是不相容的。由于 OO 是主流语言中的主要范式,因此在主流语言中具有看似不可调和的特性似乎没有意义。
在 Scala 中,模式匹配与 OO 相协调,只需将匹配操作作为对对象的方法调用即可。(事后看来相当简单,不是吗?)特别是,匹配是通过调用提取器对象上的方法来执行的,就像任何其他对象一样,它只能访问被检查对象的公共 API,因此不会破坏封装。
受 Scala 启发的模式匹配库,其中模式本身就是一流的对象(受 F# 的 Active Patterns 启发)被添加到 Newspeak 中,这是一种非常重视OO的非常动态的语言。(Newspeak 甚至没有变量,只有方法。)
请注意,正则表达式是有限形式的模式匹配的一个示例。多态方法分派也可以看作是有限形式的模式匹配的一个例子(没有提取特征)。事实上,方法分派足够强大,可以实现完整的模式匹配,Scala 尤其是Newspeak 证明了这一点(在后者中,模式匹配甚至被实现为一个库,与语言完全分离)。
这是我的 2 美分。进行简单的Option
模式匹配:
val o = Some(1)
o match {
case Some(i) => i + 1
case None => 0
}
Scala 中发生了很多事情。编译器检查您是否有详尽的匹配,i
为语句的范围创建一个新变量,当然首先以某种方式case
提取值。Option
提取值在 Java 等语言中是可行的。实现unapply
一些商定接口的方法,你就完成了。现在您可以将值返回给调用者。
将这个提取的值提供给调用者,本质上需要一个闭包,这在没有闭包支持的常规 OO 语言中不太方便。在你可能会使用观察者模式的 Java7 中,它会变得非常难看。
如果您在混合中添加 Scala 的其他模式匹配功能,例如匹配特定类型,即case i: Int =>
;需要时使用默认子句_
(无论您是否使用,编译器都必须以某种方式检查详尽性_
);额外的检查,如case i if i > 0 =>
;依此类推,从客户端使用(想想Java),它很快变得非常难看。
如果您放弃所有那些花哨的模式匹配功能,您的模式匹配将几乎处于 Javaswitch
语句的级别。
看起来,即使可能,在没有 lambda 支持和强类型系统的情况下使用匿名类来实现也是不值得的。
我会说这更多是出于历史而非技术原因。模式匹配适用于代数数据类型,代数数据类型在历史上也与函数式语言相关联。
Scala 可能是具有模式匹配的命令式语言的一个坏例子,因为它倾向于支持函数式风格,尽管它不强制执行它。
具有模式匹配的现代命令式语言的一个例子是Rust。命令式并在金属上运行,但仍具有代数数据类型、模式匹配和其他功能语言更常见的特性。但它的编译器实现比 C 编译器复杂得多