高阶函数---swift中的泛型介绍(一步步实现Map函数)
说明
本文内容均出自函数式 Swift一书, 此处整理仅仅是为了自己日后方便查看, 需要深入研究的话, 可以点进去购买, 支持原作者
本书由 王巍–新浪微博大神翻译
OneV’s Den 喵神博客
接受其它函数作为参数的函数有时被称为高阶函数. 本篇中, 将在一些来自Swift标准库中作用于数组的高阶函数中漫游. 伴随这个过程, 我们将介绍Swift的泛型, 以及展示如何将复杂计算运用于数组中.
泛型介绍
假如我们需要写一个函数, 它接受一个给定的整型数组, 通过计算得到并返回一个新数组, 新数组各项为原数组中对应的整型数据加一. 这一切, 仅仅只需要使用一个
for循环
就能非常容易地实现:
func incrementArray(xs: [Int]) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(x + 1)
}
return result
}
现在假设我们还需要一个函数, 用于生成一个每项都为参数数组对应项两倍的数组. 这同样能很容易地使用一个
for循环
来实现:
func doubleArray(xs: [Int]) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(x * 2)
}
return result
}
这两个函数有大量相同的代码, 我们能不能将没有区别的地方抽象出来, 并单独写一个体现这种模式且更通用的函数呢? 像这样的函数需要追加一个新参数来接受一个函数, 这个参数能根据各个数组项计算得到新的整型数值:
func computeIntArray(xs: [Int], transform: ((Int) -> Int) ) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(transform(x))
}
return result
}
现在, 取决于我们想如何根据原数组得到一个新数组, 我们可以向函数传递不同的参数.
doubleArray函数
和incrementArray函数
都精简为了一行调用 computeInArray的语句:
func doubleArrany2(xs: [Int]) -> [Int] {
return computeIntArray(xs: xs, transform: { (x) -> Int in
x * 2
})
}
代码仍然不像想象中的那么灵活. 假设我们想要得到一个布尔型的新数组, 用于表示原数组中对应的数字是否是偶数. 我们可能会尝试编写一些像下面这样的代码:
func isEvenArray(xs: [Int]) -> [Bool] {
computeIntArray(xs: xs) { (x) -> Int in
x % 2 == 0
}
}
不幸的是, 这段代码导致了一个类型错误. 问题在于, 我们的
computeIntArray函数
接受一个Int -> Int
类型的参数, 也就是说, 该参数是一个返回整型值的函数. 而在isEvenArray
函数的定义中, 我们传递了一个Int -> Bool
类型的参数, 于是导致了类型错误.
我们应该如何解决这个问题呢? 一种可行的方案是定义新版本的
computeIntArray函数
, 接受一个Int -> Bool
类型的函数作为参数. 类似下面这样:
func computeBoolArray(xs: [Int], transform2: (Int) -> Bool) -> [Bool] {
var result: [Bool] = []
for x in xs {
result.append(transform2(x))
}
return result
}
但是, 这个方案的扩展性并不好. 如果接下来需要计算
String
类型呢? 我们是否还需要定义一个高阶函数来接受Int -> String
类型的参数?
幸运的是, 该问题有一个解决方案: 我们可以使用_泛型_.
computeBoolArray
和computeIntArray
的定义是相同的; 唯一的区别在于类型签名(type signature). 假如我们定义一个相似的函数computeStringArray
来支持String
类型, 其函数体将会与先前两个函数完全一致. 事实上, 相同部分的代码可以用于任何类型. 我们正真想做的是写一个能够使用于每种可能类型的泛型函数:
func genericComputeArray1<T>(xs: [Int], tr 大专栏 高阶函数---swift中的泛型介绍(一步步实现Map函数)ansform: (Int) -> T) -> [T] {
var result: [T] = []
for x in xs {
result.append(transform(x))
}
return result
}
关于这段代码, 最有意思的是它的类型签名. 理解这个类型签名有助于你将
genericComputeArray1<T>
理解为一个函数族. 类型参数T的每个选择都会确定一个新函数. 该函数接受一个整型数组和一个Int -> T
类型的函数作为参数, 并返回一个[T]
类型的数组.
我们仍能进一步将这个函数一般化. 没有理由让它仅能对类型为
[Int]
的输入数组进行处理. 将数组类型进行抽象, 能得到下面这样的类型签名:
func map<Element, T>(xs: [Element], transform: (Element) -> T) -> [T] {
var result: [T] = []
for element in xs {
result.append(transform(element))
}
return result
}
这里我们写了一个
map函数
, 它在两个维度都是通用的: 对于任何Element
的数组和transform: Element -> T
函数, 它都会生成一个T
的新数组. 这个map
函数甚至比我们之前看到的genericComputeArray函数
更通用. 事实上, 我们可以通过map来定义genericComputeArray
:
func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
return map(xs: xs, transform: transform)
}
同样的, 上述函数的定义并没有什么太过特别之处: 函数接受
xs
和transform
两个参数之后, 将他们传递给map
函数, 然后返回结果. 关于这个定义, 最有意思非类型莫属.genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T]
是map函数
的一个实例, 只是它有一个更具体的类型.
实际上, 比起定义一个顶层
map
函数, 按照Swift
的惯例将map
定义为Array
的扩展更合适:
extension Array {
func map<T>(transform: (Element) -> T) -> [T] {
var result: [T] = []
for x in self {
result.append(transform(x))
}
return result
}
}
我们子啊函数的
transform
参数中所使用的Element
类型源自于Swift
的Array
中对Element
所进行的泛型定义.
作为
map(xs, transform)
的替代, 我们现在可以通过xs.map(transform)来调用Array
的map
函数:
func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
return xs.map(transform: transform)
}
其实我们并不需要自己定义像这样的map函数, 因为它已经是
Swift
标准库的一部分了(基于SequenceType
协议被定义)
顶层函数 和 类型扩展
你可能已经注意到, 在本节的函数中我们使用了两种不同的方式来声明函数: 顶层函数和类型扩展. 在一开始创建
map
函数的过程中, 为了简单起见, 我们选择了顶层函数的版本作为例子进行展示. 不过, 最终我们将map
的泛型版本定义为Array
的扩展, 这与它在Swift
标准库中的实现方式十分相似.
在
Swift
标准库最初的版本中, 顶层函数仍然是无处不在的, 但伴随Swift2.0
的诞生, 这种模式被彻底从标准库中移除了. 随着协议扩展(protocol extension)
, 当前第三方开发者有了一个强有力的工具来定义他们自己的扩展–现在我们不仅仅可以再Array
这样的具体类型上进行定义, 还可以在Sequence Type
一样的协议上来定义扩展.
我们建议遵循此规则, 并把处理确定类型的函数定义为该类型的扩展 . 这样做的优点是自动补齐更完善, 暧昧的命名更少, 以及(通常)代码结构更清晰.
下一篇
高阶函数---swift中的泛型介绍(一步步实现Map函数)的更多相关文章
- Scala进阶之路-Scala中的泛型介绍
Scala进阶之路-Scala中的泛型介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 通俗的讲,比如需要定义一个函数,函数的参数可以接受任意类型.我们不可能一一列举所有的参数类 ...
- 窥探Swift之使用Web浏览器编译Swift代码以及Swift中的泛型
有的小伙伴会问:博主,没有Mac怎么学Swift语言呢,我想学Swift,但前提得买个Mac.非也,非也.如果你想了解或者初步学习Swift语言的话,你可以登录这个网站:http://swiftstu ...
- Swift中 @objc 使用介绍
在swift 中 如果一个按钮添加点击方法 如果定义为Private 或者 定义为 FilePrivate 那么会在Addtaget方法中找不到私有方法 但是又不想把方法暴露出来,避免外界访问 ,那 ...
- Scala集合操作中的几种高阶函数
Scala是函数式编程,这点在集合操作中大量体现.高阶函数,也就是能够接收另外一个函数作为参数的函数. 假如现在有一个需要是将List集合中的每个元素变为原来的两倍,现在来对比Java方式实现和Sca ...
- 高阶函数:map()/reduce()
Python内建了map()和reduce()函数. 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clus ...
- 函数式编程(1)-高阶变成(1)-map/reduce
map/reduce Python内建了map()和reduce()函数. 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on ...
- 初学 Python(十二)——高阶函数
初学 Python(十二)--高阶函数 初学 Python,主要整理一些学习到的知识点,这次是高阶函数. #-*- coding:utf-8 -*- ''''' 话说高阶函数: 能用函数作为参数的函数 ...
- React中的高阶组件,无状态组件,PureComponent
1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...
- Swift 4 中的泛型
作为Swift中最重要的特性之一,泛型使用起来很巧妙.很多人都不太能理解并使用泛型,特别是应用开发者.泛型最适合libraries, frameworks, and SDKs的开发.在这篇文章中,我将 ...
随机推荐
- 放贷额度相关的ROI计算
违约模型得到概率估计, 将概率值划分5档, 每一档确定一个授信系数 新的授信 = 每月收入* 授信系数 - 老的授信 计算新增授信额度 计算余额损失
- [原]win10拖拽贴靠功能注册表项调查记录
win10的拖拽贴靠功能被禁用了,偶然的机会,在设置中看到了相关的设置项,如下图 直觉告诉我一定是设置注册表中的某一项,于是决定调查下具体的注册表位置.请出procmon.exe,然后关闭贴靠功能,停 ...
- Java之代码块
package com.atguigu.java3;/* * 代码块(或初始化块) * * 1. 代码块的作用:用来初始化类.对象 * 2. 代码块如果有修饰的话,只能使用static. * 3. 分 ...
- GIS开源库OpenSceneGraph(OSG)、OSGEarth、GDAL、Qt、CGAL、Boost
GIS开源有这些库:OpenSceneGraph(OSG).OSGEarth.GDAL.Qt.CGAL.Boost
- Q_Go1
Go语言的特点及优势 一.Go语言设计初衷(为什么设计Go语言?) 1.1.设计Go源是为了解决当时Google开发遇到的困难: 大量的C++代码,同时有引入了Java和Python 成千上万的工程师 ...
- 关于k8s资源类型和缩写
资源类型 缩写 描述 clusters componentstatuses cs configmaps cm daemonsets ds deployments deploy ...
- 火车进出栈 java
题目描述 一列火车n节车厢,依次编号为1,2,3,…,n.每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种. 输入 一个数,n(n<=60000) 输出 一个数s表示n节 ...
- Python||NameError: name 'reload' is not defined
多半是运行如下代码时报错: import sysreload(sys)sys.setdefaultencoding("utf-8")123这段代码是为了解决Python中中文输出出 ...
- 前端-html-长期维护
############### 前端学什么? ################ # 前端三大部分 # HTML,页面内容,学习标签 # CSS,页面样式,学习选择器和属性 # JS,页面 ...
- 微软要冷落windows是天方夜谭还是势在必行
自从90年代开始,微软就统治着PC桌面市场,真正意义上地改变了世界,在很长一段时间内,Windows就和阳光.空气.水.电能一样,成为生活必需品,无处不在,又让人感觉不到其存在,正因如此,微软的市值于 ...