Swift中的模式匹配(swiftui actionsheet)
liuian 2025-07-02 22:16 10 浏览
本文由CocoaChina--@ALEX吴浩文翻译
更新:
2015.9.19 包含关于该问题现有Swift语法的说明。
2015.9.25 添加关于标准库中现有的~>操作符的说明
其他文章系列
(1) Custom Pattern Matching(本篇)
Swift有一个很好的特性,那就是模式匹配的扩展。模式是用于匹配的规则值,如switch语句的case,do语句的catch子句,以及if、while、guard、for-in语句的条件。
例如,假设你想判断一个整数是大于、小于还是等于零,你可以用if-else if-else语句,尽管这并不美观:
let x = 10 if x > 0 { print("大于零") } else if x用switch语句会好很多,我理想的代码是这样:
// 伪代码 switch x { case > 0: print("大于零") case但模式匹配默认并不支持不等式。让我们看看能不能改变这个现状。为了使过程更加清晰,我先忽略>0的情况,用greaterThan(0)来代替它,过后我再来定义这个操作符。
扩展模式匹配
Swift的模式匹配是基于~=操作符的,如果表达式的~=值返回true则匹配成功。标准库自带四个~=操作符的重载:一个用于Equatable,一个用于Optional,一个用于Range,一个用于Interval。这些都不符合我们的需求,尽管Range和Interval很接近了,关于它们你可以看这篇文章。
所以我们要实现我们自己的~=。这个方法的原型是:
func ~=(pattern: ???, value: ???) -> Bool我们知道这个方法必须返回一个Bool,那正是我们需要的,我们需要知道这个值是否匹配模式。接下来要问我们自己的是:参数的类型是什么?
对于值,我们可以使用Int,这正是我们在之前的例子中需要的。但让我们把它一般化,让它能够接受任何类型。在我们的情况里,模式形如greaterThan或lessThan。更一般化,模式应该是一个方法,一个能够将值作为参数并返回true或false的方法。值的类型为T,所以模式的类型应为T -> Bool:func ~=(pattern: T -> Bool, value: T) -> Bool { return pattern(value) }现在我们需定义方法greaterThan和lessThan来创建模式。注意不要把模式greaterThan(0)中的0和我们想匹配的值混淆了。greaterThan的参数是模式的一部分,这个部分将在第二步中用到。举个例子,greaterThan(0) ~= x和greaterThan(0)(x)是一样的。
我们知道方法greaterThan(0)必须返回一个方法,这个方法要能接受一个值并返回Bool。所以greaterThan必须是一个方法,接受另一个值并返回之前方法。我们把参数限制成Comparable,为了能在实现中用Swift的>和
func greaterThan(a: T) -> (T -> Bool) { return { (b: T) -> Bool in b > a } }这个方法接受一个参数,调用接受不止一个参数的方法并返回,像这样的方法这被称为Curried functions。(Swift的部分实例方法就是一种Curried functions)Swift提供了一种特别的语法用于Curried functions,正如它们的名字一样形象。使用这种语法,我们的方法变成了这样:
func greaterThan(a: T)(_ b: T) -> Bool { return b > a } func lessThan(a: T)(_ b: T) -> Bool { return b这样我们有了第一个版本的switch语句:
switch x { case greaterThan(0): print("大于零") case lessThan(0): print("小于零") case 0: print("等于零") default: fatalError("不会发生") }很不错,但看看default,这个解决方案不能给编译器任何提示进行完整性检查,所以我们不得不提供一个default。如果你确定模式覆盖了每一个可能的值,在default下调用fatalError是一个不错的主意,这表明这段代码绝对不会执行到。
自定义操作符
回想一开始的想法,以及那段伪代码。理想情况下,我们想用>0和
自定义操作符存在争议,因为其他读者经常不熟悉这些,它们降低了可读性。回到我们的例子中,类似greaterThan(0)则是完全可读,所以完全可以认为不需要自定义操作符。但同时,每个人都知道>0意味着什么。所以让我们来尝试一下,但正如我们将看到的,它不会很漂亮。
我们自定义的操作符是一元的——它们只有一个操作数。同时,它们是前置操作符(而不是后置,那种操作符在操作数后的)。在一元操作符和操作数之间不能有空格,因为Swift用空格来区分一元和二元操作符。此外,不允许用作前置操作符,我们只好用别的东西代替。(>允许前置,但不是允许后置)。
我建议我们使用~>和~只是非常像箭头并不理想,但波浪号暗示了模式匹配操作符~=。其他我可以想出的操作符(如>>和
9月25日更新:我从Nate Cook那了解到操作符~>在标准库中已经存在。虽然它的实现都没有公有,但Nate发现它是用来增加集合的索引。鉴于此,为一个完全不同的目的而使用相同的操作符可能不是一个好主意。你可以选个别的。
真正的实现并不重要。我们要做的就只是声明操作符和实现方法,这些只是我们已有的方法greaterThan和lessThan的委托:
prefix operator ~> { } prefix operator ~(a: T)(_ b: T) -> Bool { return greaterThan(a)(b) } prefix func ~ Bool { return lessThan(a)(b) }这样,我们的switch语句变成:
switch x { case ~>0: print("大于零") case ~再次提醒,操作符和操作数之间没有空格。
这样已是我们的极限,很接近原始计划,但显然并不完美。
9月19日更新:Joseph Lord提醒我,Swift有一个类似的语法:
switch x { case _ where x > 0: print("大于零") case _ where x这个语法,虽然它可能不像我们定制的解决方案那么简洁,但绝对足够好,因为你不应该为这么一个简单的目的此创建一个自定义语法。然而,我们的解决方案是一般化的,能在不同的地方应用。继续往下看。
其他应用
顺便说一句,这里给出的解决方案是非常一般化的。我们重载的模式匹配操作符~=适用任何T类型和任何接受T类型返回Bool的方法。换句话说,我们的实现使得pattern ~= value和pattern(value)一样好用。更进一步,switch value { case pattern: ... }和 if pattern(value) { ... }一样好用。
检查数字奇偶性
举几个例子。首先,一个简单的例子说明了其可应用性,虽然其实际意义不大。假设你有一个方法isEven用来检查数数字是不是偶数:
func isEven(a: T) -> Bool { return a % 2 == 0 }现在:
switch isEven(x) { case true: print("偶数") case false: print("奇数") }可以变成:
switch x { case isEven: print("偶数") default: print("奇数") }注意default,下面的代码无效:
switch x { case isEven: print("偶数") case isOdd: print("奇数") } // error: Switch must be exhaustive, consider adding a default clause匹配字符串
举一个更实际的例子,假设你想要匹配一个字符串的前缀或后缀。我们先写两个方法hasPrefix和hasSuffix,它们接受两个字符串,并检查第一个参数是否是第二个参数的前缀/后缀。这些只是现有标准库中String.hasPrefix和String.hasSuffix方法的变形,只是使参数有一个方便的顺序(前缀/后缀第一,完整的字符串第二)。如果你经常使用Partial Applied Function(偏应用方法,缺少部分参数的方法)并将它们传递给其他方法,你会发现你常常需要重复出现参数来符合被调用方法的参数。烦人,但这不难。
func hasPrefix(prefix: String)(value: String) -> Bool { return value.hasPrefix(prefix) } func hasSuffix(suffix: String)(value: String) -> Bool { return value.hasSuffix(suffix) }现在我们可以这样,在我看来这很容易阅读了:
let str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" switch str { case hasPrefix("B"), hasPrefix("C"): print("以B或C开头") case hasPrefix("D"): print("以D开头") case hasSuffix("Z"): print("以Z结尾") default: print("其他情况") }结论
为了解决我们最初的问题,我们提出了一个一般化的解决方案,它可以解决很多不同的问题。我发现这种情况经常发生,当你将方法看作值来传递,它可以用在你通常想不到的地方。这是函数式编程改进可组合性这一说法背后的核心概念之一。
扩展Swift的模式匹配系统,使其有了新的功能,无论是对于内置类型还是自定义类型,都是极其强大的。一如既往,注意不要把它扩展太多。即使一个自定义的语法看上去比保守的解决方案更为干净,但对于那些不熟悉它的人它使代码更加难读了。
相关推荐
- 总结下SpringData JPA 的常用语法
-
SpringDataJPA常用有两种写法,一个是用Jpa自带方法进行CRUD,适合简单查询场景、例如查询全部数据、根据某个字段查询,根据某字段排序等等。另一种是使用注解方式,@Query、@Modi...
- 解决JPA在多线程中事务无法生效的问题
-
在使用SpringBoot2.x和JPA的过程中,如果在多线程环境下发现查询方法(如@Query或findAll)以及事务(如@Transactional)无法生效,通常是由于S...
- PostgreSQL系列(一):数据类型和基本类型转换
-
自从厂子里出来后,数据库的主力就从Oracle变成MySQL了。有一说一哈,贵确实是有贵的道理,不是开源能比的。后面的工作里面基本上就是主MySQL,辅MongoDB、ES等NoSQL。最近想写一点跟...
- 基于MCP实现text2sql
-
目的:基于MCP实现text2sql能力参考:https://blog.csdn.net/hacker_Lees/article/details/146426392服务端#选用开源的MySQLMCP...
- ORACLE 错误代码及解决办法
-
ORA-00001:违反唯一约束条件(.)错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。ORA-00017:请求会话以设置跟踪事件ORA-00018:超出最大会话数ORA-00...
- 从 SQLite 到 DuckDB:查询快 5 倍,存储减少 80%
-
作者丨Trace译者丨明知山策划丨李冬梅Trace从一开始就使用SQLite将所有数据存储在用户设备上。这是一个非常不错的选择——SQLite高度可靠,并且多种编程语言都提供了广泛支持...
- 010:通过 MCP PostgreSQL 安全访问数据
-
项目简介提供对PostgreSQL数据库的只读访问功能。该服务器允许大型语言模型(LLMs)检查数据库的模式结构,并执行只读查询操作。核心功能提供对PostgreSQL数据库的只读访问允许L...
- 发现了一个好用且免费的SQL数据库工具(DBeaver)
-
缘起最近Ai不是大火么,想着自己也弄一些开源的框架来捣腾一下。手上用着Mac,但Mac都没有显卡的,对于学习Ai训练模型不方便,所以最近新购入了一台4090的拯救者,打算用来好好学习一下Ai(呸,以上...
- 微软发布.NET 10首个预览版:JIT编译器再进化、跨平台开发更流畅
-
IT之家2月26日消息,微软.NET团队昨日(2月25日)发布博文,宣布推出.NET10首个预览版更新,重点改进.NETRuntime、SDK、libraries、C#、AS...
- 数据库管理工具Navicat Premium最新版发布啦
-
管理多个数据库要么需要使用多个客户端应用程序,要么找到一个可以容纳你使用的所有数据库的应用程序。其中一个工具是NavicatPremium。它不仅支持大多数主要的数据库管理系统(DBMS),而且它...
- 50+AI新品齐发,微软Build放大招:拥抱Agent胜算几何?
-
北京时间5月20日凌晨,如果你打开微软Build2025开发者大会的直播,最先吸引你的可能不是一场原本属于AI和开发者的技术盛会,而是开场不久后的尴尬一幕:一边是几位微软员工在台下大...
- 揭秘:一条SQL语句的执行过程是怎么样的?
-
数据库系统能够接受SQL语句,并返回数据查询的结果,或者对数据库中的数据进行修改,可以说几乎每个程序员都使用过它。而MySQL又是目前使用最广泛的数据库。所以,解析一下MySQL编译并执行...
- 各家sql工具,都闹过哪些乐子?
-
相信这些sql工具,大家都不陌生吧,它们在业内绝对算得上第一梯队的产品了,但是你知道,他们都闹过什么乐子吗?首先登场的是Navicat,这款强大的数据库管理工具,曾经让一位程序员朋友“火”了一把。Na...
- 详解PG数据库管理工具--pgadmin工具、安装部署及相关功能
-
概述今天主要介绍一下PG数据库管理工具--pgadmin,一起来看看吧~一、介绍pgAdmin4是一款为PostgreSQL设计的可靠和全面的数据库设计和管理软件,它允许连接到特定的数据库,创建表和...
- Enpass for Mac(跨平台密码管理软件)
-
还在寻找密码管理软件吗?密码管理软件有很多,但是综合素质相当优秀且完全免费的密码管理软件却并不常见,EnpassMac版是一款免费跨平台密码管理软件,可以通过这款软件高效安全的保护密码文件,而且可以...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
飞牛OS入门安装遇到问题,如何解决?
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)