Swift中的模式匹配(swiftui actionsheet)
liuian 2025-07-02 22:16 32 浏览
本文由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的模式匹配系统,使其有了新的功能,无论是对于内置类型还是自定义类型,都是极其强大的。一如既往,注意不要把它扩展太多。即使一个自定义的语法看上去比保守的解决方案更为干净,但对于那些不熟悉它的人它使代码更加难读了。
相关推荐
-
- 驱动网卡(怎么从新驱动网卡)
-
网卡一般是指为电脑主机提供有线无线网络功能的适配器。而网卡驱动指的就是电脑连接识别这些网卡型号的桥梁。网卡只有打上了网卡驱动才能正常使用。并不是说所有的网卡一插到电脑上面就能进行数据传输了,他都需要里面芯片组的驱动文件才能支持他进行数据传输...
-
2026-01-30 00:37 liuian
- win10更新助手装系统(微软win10更新助手)
-
1、点击首页“系统升级”的按钮,给出弹框,告诉用户需要上传IMEI码才能使用升级服务。同时给出同意和取消按钮。华为手机助手2、点击同意,则进入到“系统升级”功能华为手机助手华为手机助手3、在检测界面,...
- windows11专业版密钥最新(windows11专业版激活码永久)
-
Windows11专业版的正版密钥,我们是对windows的激活所必备的工具。该密钥我们可以通过微软商城或者通过计算机的硬件供应商去购买获得。获得了windows11专业版的正版密钥后,我...
-
- 手机删过的软件恢复(手机删除过的软件怎么恢复)
-
操作步骤:1、首先,我们需要先打开手机。然后在许多图标中找到带有[文件管理]文本的图标,然后单击“文件管理”进入页面。2、进入页面后,我们将在顶部看到一行文本:手机,最新信息,文档,视频,图片,音乐,收藏,最后是我们正在寻找的[更多],单击...
-
2026-01-29 23:55 liuian
- 一键ghost手动备份系统步骤(一键ghost 备份)
-
步骤1、首先把装有一键GHOST装系统的U盘插在电脑上,然后打开电脑马上按F2或DEL键入BIOS界面,然后就选择BOOT打USDHDD模式选择好,然后按F10键保存,电脑就会马上重启。 步骤...
- 怎么创建局域网(怎么创建局域网打游戏)
-
1、购买路由器一台。进入路由器把dhcp功能打开 2、购买一台交换机。从路由器lan端口拉出一条网线查到交换机的任意一个端口上。 3、两台以上电脑。从交换机任意端口拉出网线插到电脑上(电脑设置...
- 精灵驱动器官方下载(精灵驱动手机版下载)
-
是的。驱动精灵是一款集驱动管理和硬件检测于一体的、专业级的驱动管理和维护工具。驱动精灵为用户提供驱动备份、恢复、安装、删除、在线更新等实用功能。1、全新驱动精灵2012引擎,大幅提升硬件和驱动辨识能力...
- 一键还原系统步骤(一键还原系统有哪些)
-
1、首先需要下载安装一下Windows一键还原程序,在安装程序窗口中,点击“下一步”,弹出“用户许可协议”窗口,选择“我同意该许可协议的条款”,并点击“下一步”。 2、在弹出的“准备安装”窗口中,可...
- 电脑加速器哪个好(电脑加速器哪款好)
-
我认为pp加速器最好用,飞速土豆太懒,急速酷六根本不工作。pp加速器什么网页都加速,太任劳任怨了!以上是个人观点,具体性能请自己试。ps:我家电脑性能很好。迅游加速盒子是可以加速电脑的。因为有过之...
- 任何u盘都可以做启动盘吗(u盘必须做成启动盘才能装系统吗)
-
是的,需要注意,U盘的大小要在4G以上,最好是8G以上,因为启动盘里面需要装系统,内存小的话,不能用来安装系统。内存卡或者U盘或者移动硬盘都可以用来做启动盘安装系统。普通的U盘就可以,不过最好U盘...
- u盘怎么恢复文件(u盘文件恢复的方法)
-
开360安全卫士,点击上面的“功能大全”。点击文件恢复然后点击“数据”下的“文件恢复”功能。选择驱动接着选择需要恢复的驱动,选择接入的U盘。点击开始扫描选好就点击中间的“开始扫描”,开始扫描U盘数据。...
- 系统虚拟内存太低怎么办(系统虚拟内存占用过高什么原因)
-
1.检查系统虚拟内存使用情况,如果发现有大量的空闲内存,可以尝试释放一些不必要的进程,以释放内存空间。2.如果系统虚拟内存使用率较高,可以尝试增加系统虚拟内存的大小,以便更多的应用程序可以使用更多...
-
- 剪贴板权限设置方法(剪贴板访问权限)
-
1、首先打开iphone手机,触碰并按住单词或图像直到显示选择选项。2、其次,然后选取“拷贝”或“剪贴板”。3、勾选需要的“权限”,最后选择开启,即可完成苹果剪贴板权限设置。仅参考1.打开苹果手机设置按钮,点击【通用】。2.点击【键盘】,再...
-
2026-01-29 21:37 liuian
- 平板系统重装大师(平板重装win系统)
-
如果你的平板开不了机,但可以连接上电脑,那就能好办,楼主下载安装个平板刷机王到你的个人电脑上,然后连接你的平板,平板刷机王会自动识别你的平板,平板刷机王上有你平板的我刷机包,楼主点击下载一个,下载完成...
- 联想官网售后服务网点(联想官网售后服务热线)
-
联想3c服务中心是联想旗下的官方售后,是基于互联网O2O模式开发的全新服务平台。可以为终端用户提供多品牌手机、电脑以及其他3C类产品的维修、保养和保险服务。根据客户需求层次,联想服务针对个人及家庭客户...
- 一周热门
-
-
用什么工具在Win中查看8G大的log文件?
-
如何修改图片拍摄日期?快速修改图片拍摄日期的6种方法
-
RK3588-HDMIRX(瑞芯微rk3588芯片手册)
-
用纯Python轻松构建Web UI:Remi 动态更新,实时刷新界面内容
-
tplink无线路由器桥接教程(tplink路由器如何进行无线桥接)
-
windows11专业版密钥最新(windows11专业版激活码永久)
-
R语言 | CNS绘图第1款——linkET万物皆可连
-
都说Feign是RPC,没有侵入性,为什么我的代码越来越像 C++
-
如何在 Ubuntu 命令行中使用 Wireshark 进行抓包?
-
玩转命令行:7 个高效 Linux 命令技巧,助你事半功倍!
-
- 最近发表
- 标签列表
-
- 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)
