浅谈 Swift 的函数式编程

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看158 回复回复2 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 158|回复: 2
收起左侧

浅谈 Swift 的函数式编程

[复制链接]
攻城狮 发表于 2016-6-28 12:47:51 | 显示全部楼层 |阅读模式
快来登录
获取最新的苹果动态资讯
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
本帖最后由 攻城狮 于 2016-6-28 12:51 编辑

Swift 在设计上非常注重函数式思想的渗透,这使得我们在日常开发中又有了一个新的方向可以选择。很多人可能不太了解函数式,其实我之前也并没有怎么接触过函数式编程,所以本文也就是漫谈一下函数式给我们带来的便利,有错误的地方也欢迎大家指出。

现在有非常多使用函数式思想设计的库,比如大名鼎鼎的 ReactiveX,它将一系列事物抽象成信号源,你可以观察这个信号源,也可以给它发出的信号裹上一层“衣服”(也就是我们说的变换或者操作符)来得到一个新的信号源,并且这种调用是可以链式进行的,然后我们订阅这个最终的信号源,当初始的信号源发出一个信号,这个信号将经过一层层的变换,变成你想要得到的数据传递给观察者。

这么说可能会有些抽象,举个简单的例子吧,有一个 UITextField,当用户输入的时候我们拿到用户输入的值,交给一个网络请求,将网络请求的结果再交给一个解析函数,得到的结果显示到一个 UILabel 上。整个逻辑如果用传统的方式做将会出现各种状态,各种事件地响应,并且这些状态也将会被修改,如果逻辑再复杂一些,代码将变得十分复杂,这种模式我们称之为命令式编程。那我们来看看用函数式结合 ReactiveX 将会是怎样的情景:

[Swift] 纯文本查看 复制代码
textField.rx_value
    .map(someTransformer)
    .flatMap(startNetworkRequest)
    .map(anotherTransformer)
    .bindTo(label.rx_value)
    .dispose(...)


上面也只是个伪代码了,但是逻辑还是十分清晰的,所有的函数都没有产生副作用,算是比较纯粹的函数式编程了。这样我们就可以将一个复杂的逻辑流变成几行代码就能描述清楚的事件流了。这种模式就是声明式编程。

浅谈 Swift 的函数式编程

浅谈 Swift 的函数式编程 - 敏捷大拇指 - 浅谈 Swift 的函数式编程





1、Getting Started 走起来~

上面扯了这么多,没有什么干货。下面我们通过一个例子,实战一下函数式编程的实际应用。

在这之前,我们先小试牛刀一下。

假设有下面两个数组:

[Swift] 纯文本查看 复制代码
let numbers = [8, 2, 1, 0, 3]
let indexes = [2, 0, 3, 2, 4, 0, 1, 3, 2, 3, 3]


现在我们要根据 indexes 作为下标依次从 numbers 中取出数字,然后拼接成一个字符串。如果用命令式编程,我们会很容易想到下面这样的代码:

[Swift] 纯文本查看 复制代码
var temp = [Int]()
for i in indexes {
    temp.append(numbers[ i ])
}

var result = ""
for n in temp {
    result += String(n)
}

print(result)


OK,代码是能 work 的,但我认为这很糟糕,当然也很不 functional,整个逻辑中充满着命令和状态变化。

下面我们就利用函数式把它重写一下:

[Swift] 纯文本查看 复制代码
print(indexes.map({ "\(numbers[$0])" }).reduce("") { $0 + $1 })


这真的很 functional,没有任何新的中间量出现,没有任何状态变化,我们在一行里完成了上面 9 行才能完成的工作。它很好地揭示了函数式编程的核心 mapreduce 是如何工作的。

map —— 将原有元素进行一定地变换,这个变换可以是数值上的变换,也可以是类型上的变换,但唯一不能变的就是输出的维度。也就是说,如果输入是一个整型,那么输出一定也是一个什么类型,而不能是一个数组,因为 map 函数并不能帮你把这个数组展开,虽然语法上没问题,但结果一定不是你想要的。下面的 flatMap 也许能帮到你。

flatMap —— 非常类似 map,只不过这次,你可以变换维度了,经过 flatMap 函数,输入值将会变成另外一个可以被 map 操作的类型(比如数组),然后这个类型中的每个元素将会全部被展开添加到结果中去,也就是说输入可能有三个值,而输出却有更多或者更少的值了,很 magical。下面是个例子:

[Swift] 纯文本查看 复制代码
print([1, 3, 2].flatMap { [Int](1...$0) })    // [1, 1, 2, 3, 1, 2]


输入数组中的每个元素将会产生大小为它自身的一个数列,输出结果就是将这些数列拼接起来了。

reduce —— 数学上的归一化简,就是将一组数经过一定的运算变成一个数,通常我们可以用它来计算一组数的和,例如:

[Swift] 纯文本查看 复制代码
print([1, 2, 3, 4].reduce(0) { $0 + $1 })    // 10


reduce 接受一个初始值和一个函数,在这个函数中你可以拿到当前元素和当前的累加数值,并据此返回一个新的累加数值,以此类推,最终会返回最后一个累加数值。

filter —— 这个就更好理解了,根据一个函数去过滤一个数组的元素,没什么可说的。




2、What's Next? 然后呢?

Swift 是一个多范式的编程语言。下面我们结合协议、泛型,来看看如何在 Swift 中实现链式运算。

现在假设我们有三种变换:

  • 将一个数进行幂运算
  • 将一个数偶数化,如果它不是偶数则加一
  • 将一个数变成字符串


如果用常规方法,我们将这么做:

[Swift] 纯文本查看 复制代码
string(even(power(e, n)))


这很简单,但现在如果我想增加一个变换呢?我就需要修改函数调用了,显然这会比较麻烦。

下面我们利用函数式思想重构一下这个例子。

首先我们用协议将变换抽象化:

[Swift] 纯文本查看 复制代码
protocol Transformer {
    associatedtype InputType
    associatedtype OutputType
    func transform(elem: InputType) -> OutputType
}


然后实现这些变换:

[Swift] 纯文本查看 复制代码
struct PowerTransformer<T : Strideable> : Transformer {
    let n: Int

    init(n: Int) {
        self.n = n
    }

    func transform(elem: T) -> T {
        return Int(pow(Double(elem as! Int), Double(n))) as! T
    }
}

struct EvenTransformer<T : IntegerType> : Transformer {
    func transform(elem: T) -> T {
        return elem % 2 == 0 ? elem : elem + 1
    }
}

struct StringTransformer<T : Hashable> : Transformer {
    func transform(elem: T) -> String {
        return "\(elem)"
    }
}


这里我用到了泛型,如果你对泛型还不了解的话还是建议先去看看官方的 Guide。

现在做链式计算其实和之前还是一样的,但是由于我们有了变换的抽象,我们就能很容易地实现一个组合变换类型:

[Swift] 纯文本查看 复制代码
struct ComposedTransformer<T : Transformer, U : Transformer where T.OutputType == U.InputType> : Transformer {
    let transformer1: T
    let transformer2: U

    init(transformer1: T, transformer2: U) {
        self.transformer1 = transformer1
        self.transformer2 = transformer2
    }

    func transform(elem: T.InputType) -> U.OutputType {
        return transformer2.transform(transformer1.transform(elem))
    }
}


它接受两个变换,然后依次调用这两个变换,输出最后的结果。通过递归归纳,我们可以不断地组合,生成组合变换,然后那它再与其他变换组合...得到最终的组合变换我们就可以用于计算各种数值了。




3、Higher 飞得更高

但是现在创建组合变换貌似有点麻烦,因为要写很长的构造器参数,并且整个语句还是和嵌套函数调用一样。别忘了,Swift 支持自定义运算符!

[Swift] 纯文本查看 复制代码
infix operator ~> { associativity left }
func ~><T : Transformer, U : Transformer where T.OutputType == U.InputType>(t1: T, t2: U) -> ComposedTransformer<T, U> {
    return ComposedTransformer(transformer1: t1, transformer2: t2)
}


我相信不用说你也知道怎么用了。这个运算符能帮我们生成组合变换,所以我们只需要这样:

[Swift] 纯文本查看 复制代码
let powerTransformer = PowerTransformer<Int>(n: 3)
let evenTransformer = EvenTransformer<Int>()
let stringTransformer = StringTransformer<Int>()

let composedTransformer = powerTransformer ~> evenTransformer ~> stringTransformer


就已经得到了这个链式的运算组合了。

[Swift] 纯文本查看 复制代码
composedTransformer.transform(7)


直接拿来计算就可以了,并且由于运算符没有嵌套,我们可以很轻松地改变变换链的顺序,可以随意增加删除变换。是不是很方便呢?




4、Wrap Up 总而言之

当然,函数式编程的优点远不于此,我这里也只是抛砖引玉。今年 WWDC 有个 Session 很不错,Session 419 - Protocol and Value Oriented Programming in UIKit Apps,讲了如何在 UI 编程中用好值类型和协议,当然,只要牵扯到值类型的东西,都是可以和函数式扯上关系的。

总结一下,函数式编程很好,很强大,虽然是个古老的编程思想,但现代化的编程思想也无不在向其靠近,结合新的技术,恰当地使用函数式一定能为你的开发提升很多效率!在敏捷大拇指上还可以了解到Swift的更多编程思想,比如响应式。




作者:Cyandev

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

创客 发表于 2016-7-1 03:24:44 | 显示全部楼层
求一篇文章对比分析一下函数式编程与响应式编程的区别
baddy 发表于 3 天前 | 显示全部楼层
不错~~~~
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表