Swift中,函数是一等公民
问题
最近因为某个类中有重复代码,在“固化思维”重构之后,虽然原来的重复代码去掉了,但又有如下样式的代码,仔细想想,其实还是有重复,如files和dirs的获取,以及对结果的处理,代码是完全一样的。
1 | extension Array where Element: WeiyunItem { |
解决
按传统的思路来写的话,就是将相同的代码抽取到函数里,然后再调用相应函数来避免重复代码。 重构后大概的代码如下:
1 | extension Array where Element: WeiyunItem { |
看起来还ok,除了Completable.create及Disposables.create()之外,基本没有重复代码了。
继续思考
不过,这是终点了吗?并不是,毕竟还有部分代码是重复的。
我们现在换一种思路来思考,第一张截图里面,除了调用的WeiyunSDK的接口不同,传入的参数不同,其它所有代码都是一样的,那么是否可以在这儿做文章?
再回到开头看下这句话:Swift中函数是一等公民。这句话的意义是说函数也可以被操作、变换、处理等,你想到的基本都能做。
那么,把函数作为值传入处理函数中,在处理函数中调用处理就ok。通过传入不同的函数,即可实现调用不同的请求。
但有个很大的问题,函数类型不一样,restoreRecycleFile多了第2、3两个参数!
如何把restoreRecycleFile和clearRecycleFile变为具有相同参数的函数,就是要解决的问题。
今天的主角:柯里化,就是来解决这个问题的。
柯里化
柯里化是一个通用的概念,在函数式编程里面非常重要。它在维基上的定义是:
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
就作用来说,柯里化可以改变函数类型,可以提前绑定其中的参数。
Github上也有一些现成的柯里化开源库,可以直接用的。如Curry、Prelude等
可以看如何将两个参数变一个参数的简单实现,以及如何使用柯里化:
1 | public func curry<A, B, C>(_ function: @escaping ((A, B)) -> C) -> (A) -> (B) -> C { |
最终方案
在我们这个需求场景中,是需要提前绑定第2和3个参数,并且返回只接受三个参数的函数,这些开源库没有提供相应实现,不自己实现一个并不复杂:
1 | // 可以绑定2、3参数的curry化函数 |
然后把这个函数用上之后,就可以将代码整理成如下:
1 | fileprivate extension Array where Element: WeiyunItem { |
这样就没有任何重复代码了
这儿只是演示了柯里化非常简单的一种使用场景,在函数式编程中,对函数的处理变换无处不在,柯里化也会大放异彩!
严格来说,curry2_3 这种处理方式,是叫函数部分施用,有些语言里会明确区分这些概念,有些语言里(例如Groovy)没有区分。