函数式编程初探之Swift

最近接触swift之后,发现一个与Object-C区别很大的地方是Object-C里面很多Class里面都换成了Struct类型了,在自己有限的知识范围内,感觉Struct并没有Class那么好,因为大部分Struct内存分配在栈上面,Class充分利用了堆栈,似乎感觉Class要好一些,但是为什么swift会用这么多Struct呢?还有一个问题是元组,当时一个同学在改写一个数组元组的时候,感觉很别扭,整个数组都要拷贝,难道这样的效率更高,带着这样的疑问再网上找了半天,冥冥之中好像听过“不可变变量”这种说法,最后在函数式编程当中找到了一些答案,所以本文只是函数式编程的一个基础概念理解,如果有上面不对的地方,欢迎大家指正。

什么是函数式编程:

In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

这个是维基百科的解释,简单的翻译为:是一种构造程序的方式,这种方式将函数看待为数学方程,并且避免使用状态和变量。

函数式编程与其他编程方式的区别也经常叫做声明式编程与命令式编程,这两个的主要区别是;

命令式编程遇到问题的解决方式是怎么用算法,一步一步的解决这个问题,一个很形象的比喻是你有个一食谱,教你怎么一步一步的去做一道菜,要哪些原料,混合哪些东西,最后吧这道菜做出来。

声明式编程遇到问题的解决方式是有什么可以解决这个东西,而不是怎么去解决。如果用一道菜做比喻的话就是直接给你一个照片,或者告诉你这个菜是什么样子的。

函数式编程的一些基本概念:

1变量不可变和副作用

变量不可变是函数式编程与命令式编程的主要区别,如果你需要改变一个变量,那就需要重新Copy一份,在进行修改,Swift中String类型也是值类型,所以在函数中传递的时候其实是进行了值拷贝(当然是有修改的时候,编译器对这个做了优化),所以你在函数中拿到的String可以很放心的去修改这个字符串而不会被别人串改。官方文档是这么介绍的:

Swift’s copy-by-default String behavior ensures that when a function or method passes you a String value, it’s clear that you own that exact String value, regardless of where it came from. You can be confident that the string you are passed won’t be modified unless you modify it yourself.

所以这种Copy属性就会减少一部分Bug的产生,还有一个优点是减少了变量的互斥,增加了多核的运算能力,知乎上面有一张图表示了现代计算机计算能力的增长已经不依赖CPU主频的增长,而是依赖CPU核数的增多,所以这种不可变变量充分利用了这个好处。


aaa.jpg

副作用表示调用一个函数因为某种外部的原因导致在参数一致的情况下返回却不一样了,一般有全局变量的情况会导致副作用,也不允许函数去改变外部变量的状态,这样会导致程序的结果不一致。

2.模块化

函数式编程吧业务逻辑封装在一个一个的函数里面,函数不会影响到参数列表(因为都是Copy),也不会影响到外部的状态。

3.函数一等公民与高阶函数

函数式编程中函数可以像变量一样传来传去,可以作为参数也可以作为返回值,当一个函数接受函数参数是我们叫做高阶函数,swift中常用的有 Map,Reduce,Filter等。

4.柯里化

很多函数式编程的科普文章都写了这个名次,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术,比如有一个函数需要两个参数f(x,y),柯里化之后就变成两个函数,一个接受x,一个接受y,最后的行为编程f(x)(y),这个东西是由于函数式编程吧函数作为一等公民,所以f(x)返回一个函数,这个函数接受y为参数,柯里化可以实现部分计算,但是有什么用,很多人在网上讨论。柯里化在工程中有什么好处? - 知乎

f(x,y) => 柯里化 f(x)(y)

5.纯函数

函数式编程的一个重要概念就是纯函数,这种函数有两个重要的标准。

1.在输入一定的情况下输出确定。

2.不会对函数外产生副作用。

func add_pure(a: Int) -> Int {
        return a + 1
    }

上面的函数是纯函数,输入确定之后输出确定,并且不会改变外面变量的状态

var b = 0;
func add_nopure(a: Int) -> Int {
    return a + b
}

上面的函数不是纯函数,因为返回值收到b的影响。

6.引用透明

引用透明和纯函数概念差不多,引用透明导致一定的输入与输出是不会改变的,这样可以方便编译去做优化。

7.递归

递归用于替代在命令式编程里面的循环,由于递归会导致栈益处,所以在函数式编程中,很多编译器都会用尾递归调用来优化。尾调用优化 - 阮一峰的网络日志

下面我们用一个例子来说明命令式编程与函数式编程的区别
假如有一个学籍成绩表单

enum GenderType {
    case boy
    case girl
}

struct Student{
    let name: String
    let gender: GenderType
    let source: Float
}

let students = [
        Student(name: "Make", gender: .boy, source: 75.0),
        Student(name: "Jason", gender: .boy, source: 80.0),
        Student(name: "Lucy", gender: .girl, source: 82.0),
        Student(name: "Lili", gender: .girl, source: 83.0),
        Student(name: "Amy", gender: .girl, source: 70.0),
        Student(name: "Jenny", gender: .girl, source: 72.0),
        Student(name: "Kelly", gender: .girl, source: 90.0),
        Student(name: "Helen", gender: .girl, source: 170.0),
    ]

我们现在有一个需求,要获取所有已女学生的成绩姓名排名:
如果按照命令式的编程方式,
第一步:过滤所有女学生
第二部:排序所有女学生
第三部:吧所有女学生的名字放入数组返回

func getUpScoreName(studets : [Student] , type: GenderType)-> [String] {
        
       var genderStudent = [Student]()
       var genderName = [String]()

       for i in 0..<studets.count {
            if studets[i].gender == type {
                genderStudent.append(studets[i])
            }
        }
        
        for i in 0..<genderStudent.count {
            let stu = genderStudent[i]
            //2
            for j in stride(from: i, to: -1, by: -1){
                if stu.source < genderStudent[j].source {
                    genderStudent.remove(at: j + 1)
                    genderStudent.insert(stu, at: j)
                }
            }
        }
        
        for i in 0..<genderStudent.count {
            genderName.append(genderStudent[i].name)
        }
        
        return genderName
    }

可能代码会是这样的。
如果我们用函数式编程的结果,那可能这么去实现:
让Student实现对比接口:

extension Student : Comparable {
    static func ==(lhs: Student, rhs: Student) -> Bool {
        return false
    }
    
    static func < (lhs: Student, rhs: Student) -> Bool {
        return lhs.source < rhs.source
    }
}

获取排序结果:

    func getUpScoreNameFC(studets : [Student] , type: GenderType) -> [String] {
        let names = studets.filter{ $0.gender == type }.sorted{ $0 < $1 }.map{ return $0.name }
        return names
    }

这样的代码看起来就是我需要什么,而不是我要一步一步的怎么去实现里面的东西,虽然Swift给我们提供的Map,reduce等操作接口是系统API,但是我们自己写方法的时候可以忘这方面靠近,达到这种一看代码就知道再干什么,不需要一步一步去分析代码是什么,一目了然的结果。

第二个例子是怎么消除局部可变变量,函数式编程要求变量不可变,而且不需要变量,函数是纯函数,这样函数可以不受外部的影响。
加入我们现在要给班级里面的三个学生发小红花:
如果是命令式编程方式:

  var count = 0
        mutating func flower() {
              getFlowerToStudent(studets: self.students, countTotal: 3)
        }
       mutating func getFlowerToStudent(studets : [Student] , countTotal: Int) {
        self.count = countTotal
        let max = UInt32(studets.count)
        while count > 0 {
            let index =  Int(arc4random() % max)
            count = count - 1
            print(studets[index])
        }
    }

我们首选申请一个局部变量,记录要给多少小朋友发红花,然后在循环里面去减去这个变量,已判断是否发送完毕。现在的逻辑还不复杂,但是如果逻辑很多的时候,又需要变量去维持状态的时候,阅读代码就很困难,而且很容易因为状态变量改变而出现Bug。

如果是函数式编程可能就是这种结果:

  mutating func flower() {
        getFlowerToStudentFC(studets: self.students, countTotal: 3)
    }

    func getFlowerToStudentFC(studets : [Student], countTotal: Int) {
        if (countTotal == 0) {
            return
        }else {
            let index =  Int(arc4random() %  UInt32(studets.count))
            print(studets[index])
            self.getFlowerToStudentFC(studets: studets, countTotal: countTotal - 1)
        }
    }

没有状态变量,所有的函数都是纯函数,这样阅读起来可以根据函数名称很好的理解函数在干什么。

总结:
swift不是纯函数式编程的语言,但是再往函数式编程方向靠拢,我们在写代码的时候可以从我们底层的很model,或者viewmodel等简单的模块尝试用函数式编程这种方式。

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

推荐阅读更多精彩内容