Swift有一个很好的特性,那就是模式匹配的扩展。模式是用于匹配的规则值,例如switch语句的case、do语句的catch子句、if、while、guard、for-in语句的条件等。例如,假设要判断一个整数是否大于、小于或等于0,可以使用if-elseif-else语句,虽然这样不美观:letx=10ifx>0{print("大于zero")}elseifx<0{print("lessthanzero")}else{print("equaltozero")}用switch语句会好得多,我理想的代码是这样的://pseudocodeswitchx{case>0:print("大于零")case<0:print("小于零")case0:print("等于零")}但是模式匹配默认不支持不等式。让我们看看我们是否可以改变这种情况。为了让流程更清晰,我忽略了>0的情况,将其替换为greaterThan(0),稍后我会定义这个运算符。扩展模式匹配Swift的模式匹配是基于~=运算符的。如果表达式的~=值返回true,则匹配成功。标准库带有~=运算符的四种重载:一种用于Equatable,一种用于Optional,一种用于Range,一种用于Interval。这些都不符合我们的需求,尽管Range和Interval非常接近,你可以阅读这篇关于它们的文章。所以我们要实现我们自己的~=.这个方法的原型是:func~=(pattern:???,value:???)->Bool我们知道这个方法必须返回一个Bool,这正是我们需要的,我们需要知道value是否匹配模式。接下来要问自己的是:参数的类型是什么?对于值,我们可以使用Int,这正是我们在前面的示例中所需要的。但是让我们概括它,以便它可以接受任何类型。在我们的例子中,模式看起来像greaterThan(001.png)或lessThan(001.png)。更一般地说,模式应该是一种方法,一种将值作为参数并返回true或false的方法。值的类型是T,所以模式的类型应该是T->Bool:func~=(pattern:T->Bool,value:T)->Bool{returnpattern(value)}现在我们需要定义greaterThan和lessThan方法创建模式。注意不要将模式greaterThan(0)中的0与我们要匹配的值混淆。greaterThan参数是将在第二步中使用的模式的一部分。例如,greaterThan(0)~=x与greaterThan(0)(x)相同。我们知道greaterThan(0)方法必须返回一个接受值并返回Bool的方法。所以greaterThan必须是一个接受另一个值并返回前一个方法的方法。我们将参数限制为Comparable,以便在实现中使用Swift的>和<运算符:funcgreaterThan(a:T)->(T->Bool){return{(b:T)->Boolinb>a}}这个方法接受一个参数,调用一个接受多个参数并返回的方法,这样的方法称为Curried函数。(Swift中的某些实例方法是一种Curried函数)Swift为Curried函数提供了一种特殊的语法,就像它们的名称一样。使用这种语法,我们的方法变成这样:funcgreaterThan(a:T)(_b:T)->Bool{returnb>a}funclessThan(a:T)(_b:T)->Bool{returnb0和<0替换greaterThan(0)和lessThan(0)。自定义操作符是有争议的,因为其他读者通常不熟悉这些操作符并且它们降低了可读性。回到我们的例子,像greaterThan(0)这样的东西是完全可读的,所以可以认为不需要自定义运算符。但与此同时,每个人都知道>0意味着什么。因此,让我们尝试一下,但正如我们将要看到的那样,它不会很漂亮。我们的自定义运算符是一元的——它们只有一个操作数。此外,它们是前运算符(而不是后运算符,运算符位于操作数之后)。一元运算符和它们的操作数之间不能有空格,因为Swift使用空格来区分一元运算符和二元运算符。此外,<不允许作为前缀运算符,因此我们必须使用其他东西来代替。(>允许前置,但不允许后置)。我建议我们使用~>和~<。虽然~>非常像箭头并不理想,波浪号暗示模式匹配运算符~=。我能想到的其他运算符(例如>>和<<)令人困惑。9月25日更新:我从NateCook那里得知运算符~>已经存在于标准库中。虽然它的实现都不是公开的,但Nate发现它被用来增加集合的索引。鉴于此,将相同的运算符用于完全不同的目的可能不是一个好主意。你可以选择个别的。真正的实施并不重要。我们所要做的就是声明运算符和实现方法,它们只是对我们现有方法greaterThan和lessThan的委托:prefixoperator~>{}prefixoperator~<{}prefixfunc~>(a:T)(_b:T)->Bool{returngreaterThan(a)(b)}prefixfunc~Bool{returnlessThan(a)(b)}这样我们的switch语句就变成了:switchx{case~>0:print("大于零")case~<0:print("lessthanzero")case0:print("equaltozero")default:fatalError("willnothappen")}同样,运算符和操作数之间没有空格。这是我们的极限,非常接近原计划,但显然还不够完美。9/19更新:JosephLord提醒我Swift有类似的语法:switchx{case_whereex>0:print("greaterthanzero")case_whereex<0:print("lessthanzero")case0:print("equaltozero")default:fatalError("willnothappen")}这种语法虽然可能不如我们的自定义解决方案那么简洁,但绝对足够好,因为您不应该为如此简单的目的创建自定义语法。但是,我们的解决方案是通用的,可以应用于不同的地方。继续阅读。其他应用顺便说一句,这里介绍的解决方案非常通用。我们的重载模式匹配运算符~=适用于任何类型T和任何接受类型T并返回Bool的方法。换句话说,我们的实现让pattern~=value和pattern(value)一样有效。此外,switchvalue{casepattern:...}与ifpattern(value){...}一样有效。检查号码奇偶性给出了几个例子。首先用一个简单的例子来说明它的适用性,尽管它的实际意义不大。假设你有一个方法isEven来检查数字是否为偶数:funcisEven(a:T)->Bool{returna%2==0}现在:switchisEven(x){casetrue:print("even")casefalse:print("Odd")}可以变成:switchx{caseisEven:print("Even")default:print("Odd")}注意默认,下面的代码是无效的:switchx{caseisEven:print("Even")caseisOdd:print("Odd")}//error:Switchmustbeexhaustive,consideraddingdefaultclauseMatchingStrings举个更实际的例子,假设你要匹配一个字符串的前缀或后缀。我们先写两个方法,hasPrefix和hasSuffix,接受两个字符串,检查第一个参数是否是第二个参数的前缀/后缀。这些只是标准库中现有String.hasPrefix和String.hasSuffix方法的变体,只是为了让参数有一个方便的顺序(前缀/后缀***,完整的字符串第二)。如果你经常使用PartialAppliedFunctions(部分应用的方法,缺少一些参数的方法)并将它们传递给其他方法,你会发现你经常需要重复参数来匹配被调用方法的参数。烦人,但这并不难。funchasPrefix(prefix:String)(value:String)->Bool{returnvalue.hasPrefix(prefix)}funchasSuffix(suffix:String)(value:String)->Bool{returnvalue.hasSuffix(suffix)}现在我们可以这样做了,在我认为这很容易阅读:BegindswithD")casehasSuffix("Z"):print("EndswithZ")default:print("Othercases")}结论为了解决我们原来的问题,我们提出了一个通用的解决方案,它可以解决许多不同的问题。我发现这种情况经常发生,当你按值传递一个方法时,它可以以你通常不会想到的方式使用。这是函数式编程提高可组合性这一说法背后的核心概念之一。使用新功能扩展Swift的模式匹配系统,包括内置类型和自定义类型,这些功能非常强大。一如既往,小心不要过度拉伸它。尽管自定义语法可能看起来比保守的解决方案更清晰,但对于不熟悉它的人来说,它会使代码更难阅读。
