模式匹配——处理 if 语句的噩梦
我想我们都遇到过(或写过)那种用20行if语句来检查单个变量所有可能性的情况,而且我想我们都受够了在函数中添加多一条语句会让它更加难以理解。好吧,在本文中,我们将看到一种我认为更好的方法,叫做模式匹配,本质上我们将学习如何使用switch cases with steroids
!!!
目录
什么是模式匹配以及为什么我们需要它?
一般来说,模式匹配是一种表达两个术语之间相等性的机制。我们经常在各种编程语言中使用基本的模式匹配,例如简单的模式匹配if statements
,以及更具体的模式匹配,例如destructuring
。然而,这些示例都遵循一个基本概念:模式匹配是一种表达等价关系的方法。
本文旨在探讨为什么有必要在这些基本概念之外拓展我们对模式匹配的理解。if statements
仅仅理解这些概念就足够了吗?我认为它们还不够,我打算阐述 Ruby 和 Elixir 等语言中实现的高级模式匹配技术的显著优势。我希望通过这一探索,让读者相信这些新型模式匹配结构的变革力量。
为了方便大家理解,我会举例说明上一段提到的初始示例,这样我们就可以一起继续了。首先,模式匹配的更简单形式可能是 if 语句,在 Ruby 中可以这样表达:
if 1 == 2
puts 'OMG!!!'
end
请注意,可以在处查看相等表达式1 == 2
,在这种特定情况下将始终为假。
模式匹配的其他重要形式可以在destructuring
对象中找到,我们可以在 ruby 中按如下方式进行:
irb(main):001:0> a,b,c = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> a
=> 1
irb(main):003:0> b
=> 2
irb(main):004:0> c
=> 3
irb(main):005:0>
在我看来,这真是太棒了!所有那些相等的例子都在这里变得显而易见。看看这个:数组有三个元素,我们在左侧用三个“槽位”(a、b、c)表示?这使得表达式在数学上正确无误,因此 Ruby 解释器能够完美理解我们的意图,并将每个数组元素正确地分配给每个“槽位”(如 REPL 示例的其余部分所示)。
Ruby 如何提供良好的模式匹配
我们刚刚探索了 Ruby 在解构等高级技术方面的功能,现在让我们进一步探讨模式匹配。从 Ruby 2.7 开始,该语言引入了大量旨在改进模式匹配式编码的功能。这些新增功能,包括case..in
语句和验证运算符的引入,都受到了 Elixir 的影响(我们稍后会详细介绍)。
案例,用类固醇换案例
Switch Case 在每种语言中都是很常见的模式,对吧?大多数时候,它们被视为 if 语句的另一种编写方式,因为 Rubyin
在 Switch Case 语句中使用子句添加了模式匹配支持,从而允许更复杂的交互,如下例所示:
def check_object(obj)
case obj
in String => str
puts "It's a string: #{str}"
in Integer => num
puts "It's an integer: #{num}"
in Array => [first, *rest]
puts "It's an array with the first element #{first} and the rest #{rest.inspect}"
in Hash => { key: k, value: v }
puts "It's a hash with key '#{k}' and value '#{v}'"
else
puts "It's something else."
end
end
如您所见,我们可以使用该in
子句在同一个语句中同时进行类型检查和解构来匹配特定变量!这使得我们能够通过程序以简洁直接的方式轻松控制状态。
验证运算符,一行代码即可验证所有内容
虽然模式匹配对于类似 if 语句的方法非常有用,但我们也可以将其用于更简单的验证层。从 Ruby 3 开始,我们可以=>
在 switch case 语句的作用域之外使用相同的运算符,从而允许运行时检查作为验证点。这允许我们开发人员通过使用一行简洁的模式匹配代码来简化所有这些验证类和 gem,如下所示:
irb(main):003:0> {a: 1,b: 2,c: 3} => {a: '1', b: '2', c: '3'}
(irb):3:in `<main>': {:a=>1, :b=>2, :c=>3}: "1" === 1 does not return true (NoMatchingPatternError)
from /Users/cherryramatis/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.7.4/exe/irb:9:in `<top (required)>'
from /Users/cherryramatis/.asdf/installs/ruby/3.2.2/bin/irb:25:in `load'
from /Users/cherryramatis/.asdf/installs/ruby/3.2.2/bin/irb:25:in `<main>'
irb(main):004:0> {a: 1,b: 2,c: 3} => {a: 1, b: 2, c: 3}
=> nil
在第一个例子中,我们得到了一个运行时错误检查,因为左侧与{a: 1, b: 2, c: 3}
右侧不匹配{a: '1', b: '2', c: '3'}
。这开启了一系列的可能性。只需用 Rails 中的一个对象更改右侧params
,然后用一行简单的代码随意验证即可;Ruby 真是太棒了!
顺便说一句,如果您想深入了解 Ruby 3 解构和新运算符的具体细节,我强烈建议您在此处阅读所有相关内容:https://www.fullstackruby.dev/ruby-3-fundamentals/2021/01/06/everything-you-need-to-know-about-destructuring-in-ruby-3/
Elixir 模式匹配,让人兴奋不已
到目前为止,我已经用了很大一部分篇幅来讨论我挚爱的语言 Ruby。然而,本文不仅仅讨论 Ruby,也讨论 Elixir。不过,你可能会想,为什么是 Elixir?Elixir 带来了许多可能性,这要归功于Jose Valim,他将模式匹配的概念融入了整个语言。乍一看,这可能有点奇怪,但我们会进一步探讨这些疯狂的想法。
基本作业
由于 Elixir 是一种以模式匹配为思维模型的语言,因此每次赋值都涉及通过相等性进行模式匹配的过程。这非常强大,因为像解构这样的高级概念在 Elixir 中变得非常简单,因为它只是一个普通的赋值操作。例如:
iex(1)> {a, b} = {1,2}
{1, 2}
iex(2)> a
1
iex(3)> b
2
看看解构是如何变得像创建一个新变量一样简单的?我们只需要期望一边是一个“槽”的元组{a,b}
,另一边是一个具体值的元组{1,2}
,由于它们的格式匹配,elixir 可以正确地填充这些槽,将它们转换为变量。
为了进一步证明这一点,如果我们尝试通过在任何一侧添加一个元素来破坏相等性,则运行时将对我们因这种误解而产生错误,但同样不需要任何特殊运算符:
iex(1)> {a,b} = {1,2,3}
** (MatchError) no match of right hand side value: {1, 2, 3}
(stdlib 5.0.2) erl_eval.erl:498: :erl_eval.expr/6
iex:4: (file)
iex(1)> {a,b,c} = {1,2}
** (MatchError) no match of right hand side value: {1, 2}
(stdlib 5.0.2) erl_eval.erl:498: :erl_eval.expr/6
iex:4: (file)
函数参数匹配
该语言提供的另一种可能性是,为同名函数的参数定义模式匹配。这看起来可能有点奇怪,但事实证明,这是该语言的一个非常酷的功能,并且为设计新的架构决策提供了有趣的可能性。基本上,你可以多次定义同名函数,Elixir 会使用模式匹配根据参数来决定每次执行哪个函数。
例如,假设我们要定义一个计算器模块。你首先想到的应该是为每个可能的操作定义一个方法,对吧?但请允许我通过下面的例子提供另一种思路:
defmodule Math do
def calc(:sum, num1, num2) do
num1 + num2
end
def calc(:subtract, num1, num2) do
num1 - num2
end
def calc(:divide, num1, num2) do
num1 / num2
end
def calc(:multiply, num1, num2) do
num1 * num2
end
def calc(_, _, _) do
{:error, "Action not implemented"}
end
end
看到了吗?通过思考模式匹配,我们可以设计一个更加简洁的 API,并允许逐步添加新功能。使用此提案,我们可以简单地调用示例方法名称calc
,并期望第一个原子参数定义需要执行的操作,以及后面两个数字。此外,我们甚至可以定义一个默认匹配,并在最后一个calc
定义中返回“未实现”的错误,这使我们能够逐步定义新的操作,而不必担心无法为使用此模块的任何人提供良好的体验。
结论
本文的主要目的是对 Ruby 和 Elixir 中的模式匹配进行引人入胜的介绍。我坚信两个社区都能从彼此身上获益良多,而我的 Elixir 学习之旅也充满乐趣。希望本文对任何阅读它的人都能有所帮助。愿原力与你同在!🍒
文章来源:https://dev.to/cherryramatis/pattern-matching-dealing-with-the-if-statement-nightmare-kkb