swift3函数式编程

甩锅申明(原文哪里的我忘记了,等会补上)

本文假设你熟悉swift3

关于函数式编程的介绍

FP(Functional programming)是一种编程范式。与声明式编程不同的是,它强调不可变性(immutable),纯函数(pure function),引用透明(referentially transparent)等等。如果你看到这些术语一脸懵逼,请不要灰心。本文的目的就是介绍这些。swift具有相当多的FP特性,你不应该对这些优秀的性质视而不见。

1.函数是一等公民

什么是一等公民呢(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。(作者原文说所有语言都是这样)
so 我们可以这样

func bar() {
    print("foo")
}

let baz = bar

baz() // prints "foo"

也可以这样

func foo() {
    print("foo")
}

func bar() {
    print("bar")
}

func baz() {
    print("baz")
}

let functions = [
    foo,
    bar,
    baz
]

for function in functions {
    function() // runs all functions
}

是的,你没看错,函数也能丢到数组里面,你可以做一大堆很cooooool的事情在这种语言里
顺便我们现在来定义一个概念:
Anynonmous(匿名函数) functions a.k.a Closures(闭包)
闭包(closure)是所有函数式语言都具备的一项平常特性,可是相关的论述却常常充斥着晦涩乃至神秘的字眼。所谓闭包,实际上是一种特殊的函数,它在暗地里绑定了函数内部引用的所有变量。换句话说,这种函数(或方法)把它引用的所有东西都放在一个上下文里“包”了起来(原文解释得太简单了)。
闭包在swift有很多种定义的方式(我数了下至少有5,6种吧),最常见的一种方式是

{(parameter1 : Type, parameter2: Type, ..., parameterN: Type) -> ReturnType in 

}

他们也可以定义成

let anonymous = { (item: Int) -> Bool in 
    return item > 2
}

anonymous(3)

哇哇 你不是说函数是匿名的吗,现在怎么有个名字了。
他们确实没有名字,这里只是把一个匿名函数赋值给了一个变量,就像我们之前干的那样,你看下面代码。

({ (item: Int) -> Bool in 
    return item > 2
}(4)) // returns true(有点像js的立即执行函数)

现在他是一个真正的匿名函数了,我们在定义的时候同时也执行了这个函数。同时值得注意的是内个匿名函数都有他自己的函数类型,函数类型大概看起来是这样的。

let anon : (Int, Bool) -> String = { input,condition in 
    if condition { return String(input) }
    return "Failed"
}

anon(12, true) // returns "12"
anon(12, false) // returns "Failed"

其中的我们定义的类型(可以省略,但是不建议,xcode在解析复杂类型时候会白板):

(Int, Bool) -> String

这表示这个函数接受一个Int和BooL俩个参数并且返回一个String类型,由于函数有类型,编译器在编译的时候会确保类型安全,你可以不写类型,但是类型一定要对(非常不建议不写类型,xcode在解析复杂类型时候会白板):
你可以这么写

var functionArray = [(Int, Bool)->String]()

let one : (Int, Bool) -> String = { a, b in 
    if b { return "\(a)"}
    else { return "Fail" }
}

functionArray.append(one)

let two = { (a: Int, b: Bool) -> String in 
    if b { return "\(a)" }
    else { return "FailTwo" }
}

functionArray.append(two)

上面的代码都可以工作,但是你不能这写:

let three : (Int)->Bool = { item in 
    return item > 2
}

functionArray.append(three)

你会得到错误信息:

ERROR at line 21, col 22: cannot convert value of type '(Int) -> Bool' to expected argument type '(Int, Bool) -> String'
functionArray.append(three)

大概意思就是类型不对应,你写swift代码时候将会大量面对这这种类型问题,会持续的折磨你。
这种类型语法有时候显得很啰嗦,我们可以用typealias操作符来重新定义一个复杂的类型。

typealias Checker = (Int, Bool) -> String

var funcArray = [Checker]()

let one : Checker = { a, b in 
    if b { return "\(a)" }
    return "Fail"
}

funcArray.append(one)

还有个额外的语法糖,在闭包中可以用$0,$1,$2等来替代函数参数,这种简洁的表达形式,会使我们的代码看上去很干净。现在改写下one函数

let two: Checker = { return $1 ? "\($0)" :  "Fail" }

这里面$0,来替代Int,$1替代了Bool
如果你的函数只有一行return也可以不写

2 pure function

纯函数的概念很简单 你可以简单理解为一个数学函数y=f(x)(使对于集合A中的任意一个数x,在集合B中都有唯一确定的数和它对应),也就是说对于一个给定的输入x,函数的输出是唯一的,不依赖于外部状态。同时也不会改变外部的状态。
例如:

func countUp(currentCount: Int) -> Int {
    return currentCount + 1
}

这就是个纯函数

var counter = 0
func countUp() -> Int{
    counter += 1
    return counter
}

但是他不是,他改变了外部的状态。纯函数,变量的不可变性会让你的测试代码变得很容易编写。同时他也很适合并发编程,变量的状态只依赖于他创建的时刻,再也不需要那些幺蛾子的临界区这种东西了。

3. Higher order functions

简而言之,高阶函数就是将其他函数作为参数或者返回类型是一个函数的函数,有了他,你再也不用知道数据是从哪里来了,每一个函数都是为了用小函数组织成更大的函数,函数的参数也是函数,函数返回的也是函数,最后得到一个超级牛逼的函数,最后数据灌进去了。
举个栗子

func filter(sequence: [Int], elementsSmallerThan border: Int) -> [Int] {
    var newSequence = [Int]()
    for item in sequence {
        if item < border {
            newSequence.append(item)
        }
    }
    return newSequence
}

filter(sequence: [1,2,3,4], elementsSmallerThan: 3) // returns [1,2]

这个函数筛选比3小的,现在pm改需求了,要比2大。没事在写一个

func filter(sequence: [Int], elementsLargerThan border: Int) -> [Int] {
    var newSequence = [Int]()
    for item in sequence {
        if item > border {
            newSequence.append(item)
        }
    }
    return newSequence
}

filter(sequence: [1,2,3,4], elementsLargerThan: 2) // returns [3,4]

要是在改需求呢?难道继续改代码吗
看看用高阶函数是怎么做的

func filter(sequence: [Int], condition: (Int)->Bool) -> [Int] {
    var newSequence = [Int]()
    for item in sequence {
        if condition(item) {
            newSequence.append(item)
        }
    }
    return newSequence
}

let sequence = [1,2,3,4]

let smaller = filter(sequence: sequence, condition: { item in
    item < 3
})

let larger = filter(sequence: sequence, condition: { item in
    item > 2
})

print(smaller) // prints [1,2]
print(larger) // prints [3,4]

现在随便PM怎么改需求,我们只要传递一个函数进去就行了
我们也可以用swift尾闭包的语法糖

// 大概就是闭包是最后一个参数,可以这么写
let equalTo = filter(sequence: sequence) { item in item == 2}

// 一个意思,语法糖而已
let isOne = filter(sequence: sequence) { $0 == 1}

上面写法其实也有很大的局限,因为你只能传入一个函数,不能传递一个带参数的函数,这么说有点抽象,请对着代码理解,下面代码其实使用了柯里化。

typealias Modifier = ([Int]) -> [Int]

func chooseModifier(isHead: Bool) -> Modifier {
    if isHead {
        return { array in 
            Array(array.dropLast(1))
        }
    } else {
        return { array in 
            [array[0]]
        }
    }
}

let head = chooseModifier(isHead: true)
let tail = chooseModifier(isHead: false)
//这个函数干的事情和函数的命名我有点蒙蔽....
//现在head和tail就是一个函数了,他们的函数签名是([])->[]
//head([1,2,3]) tail([1,2,3])自己试试看效果

我们来定义一个检测一个数时候某个range里的函数

typealias Range = (Int) -> Bool 
func range(start: Int, end: Int) -> Range {
    return { point in 
        return (point > start) && (point < end)
    }
}

let fourToFifteen = range(start: 4, end: 15)
//Check if a number belongs to a range
print(fourToFifteen(14)) //true
print(fourToFifteen(16)) //false 和上面同理

Conditional parameter evaluation
现在有种情况,你需要根据用的选择来生成一个参数,但是计算的代价非常昂贵。不用担心你现在可以使用高阶函数来使计算推迟到你确定需要使用时,

func stringify1(condition: Bool, parameter: Int) -> String {
    if condition {
        return "\(parameter)"
    }
    return "Fail"
}

func stringify2(condition: Bool, parameter: ()->Int) -> String{
    if condition {
        return "\(parameter())"
    }
    return "Fail"
}

let a = stringify1(condition: false, parameter: expensiveOperation())
let b = stringify2(condition: false, parameter: { return expensiveOperation()})

第一种是我们常用的形式,你只要调用这个函数就会调用判断函数。
第二种是高阶函数版本,你不走这个判断分支,判断函数是不会走的。
这种凌乱的写法会让人很难掌握,但是swift有个内置的机制来帮我们处理这些乱七八糟的类型,不要你操心
@autoclosure annotation
恩,上面说的就是@autoclosure ,他会自动的把一个表达式转换成一个closure
上代码

func stringify3(condition: Bool, parameter: @autoclosure ()->Int) -> String {
    if condition {
        return "\(parameter())"
    }
    return "Fail"
}

现在我们如果想调用stringify3:

stringify3(condition: true, parameter: 12)

上面代码有点不好理解 我在举个栗子


func test(_ p:()->Bool) {
    if p() {
        
    }
}

test({return 3>1})
test({ 3>1})
test{ 3>1}

func test1( _ p:@autoclosure ()->Bool) {
    if p() {
        
    }
}

test1(3>1)

请自己体会下,这有点抽象。。。

4. Currying

柯里化最大的好处是可以把多参数函数映射到单参数函数,把一个非常复杂的函数分解成一个个简单函数
来看一个最基本的栗子

func add(_ a: Int) -> ((Int)-> Int) {
    return { b in 
        return a + b
    }
}

add(2)(3) // returns 5

定义个很普通的add函数,然后调用add(2)(3)这不是什么奇怪的语法糖,而是add(2)本来就是个函数,你可以这么理解

let function = add(2)
function(3)

继续看代码

typealias Modifier = (String)->String

func uppercase() -> Modifier {
    return { string in 
        return string.uppercased()
    }
}

func removeLast() -> Modifier {
    return { string in 
        return String(string.characters.dropLast())
    }
}

func addSuffix(suffix: String) -> Modifier {
    return { string in 
        return string + suffix
    }
}

定义了一大丢乱七八糟的函数 返回值都是(string)-> string

let uppercased = uppercase()("Value")
let removed = removeLast()(uppercased)
let withSuffix = addSuffix("")(removed)

用科里化的写法

let a = addSuffix(suffix: "suffix")(removeLast()(uppercase()("IntitialFunction")))

是不是很神奇?下面的这段有点抽象,有个很炫酷的学术名字compose monad,不要理解这是什么东西,只要知道他是把俩个函数组合在了一起。

func compose(_ left: @escaping Modifier, _ right: @escaping Modifier) -> Modifier {
    return { string in 
        left(right(string))
    }
}

现在函数就长这样了

let a = compose(compose(uppercase(), removeLast()), addSuffix(suffix: "Abc"))("IntitialValue")

这东西还是太难懂了,太多的括号看着碍眼,有俩个方法一个是定义个科里化操作符,一种是将Modifier装在一个容器中

struct Modifier {
    
    private let modifier: (String) -> String
    
    init() {
        self.modifier = { return $0 }
    }
    
    private init(modifier: @escaping  (String)->String) {
        self.modifier = modifier
    }
    
    var uppercase: Modifier {
        return Modifier(modifier: { string in 
            self.modifier(string).uppercased()
        })
    }

    var removeLast : Modifier {
        return Modifier(modifier: { string in 
            return String(self.modifier(string).characters.dropLast())
        })
    }

    func add(suffix: String) -> Modifier {
        return Modifier(modifier: { string in 
            return self.modifier(string) + suffix
        })
    }
    
    func modify(_ input: String) -> String {
        return self.modifier(input)
    }
}

// The call is now clean and clearly states which actions happen in 
// which order
let modified = Modifier().uppercase.removeLast.add(suffix: "suffix").modify("InputValue")

print(modified)

有了这个Modifier还能这么玩

let modified = Modifier().add(suffix: "Initial").uppercase.add(suffix: "Later").removeLast.removeLast.removeLast.add(suffix: "Other").modify("Value")

The last example which uses struct breaks the functional pattern a little bit since it holds the private modifier variable. It sacrifices some of the safety for a little bit of syntactic sugar. (没理解),下面用科里化操作符来重写一次,他看起来是

ypealias Modifier = (String)->String

func uppercase() -> Modifier {
    return { string in 
        return string.uppercased()
    }
}

func removeLast() -> Modifier {
    return { string in 
        return String(string.characters.dropLast())
    }
}

func addSuffix(suffix: String) -> Modifier {
    return { string in 
        return string + suffix
    }
}

precedencegroup CurryPrecedence {
    associativity: left
}

infix operator |>  : CurryPrecedence

func |> ( left: @escaping Modifier, right: @escaping Modifier) -> Modifier {
    return { string in 
        right(left(string))
    }
}

let modified = uppercase() |> removeLast() |> addSuffix(suffix: "123")
print(modified("Abcd"))
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容