swift之函数式编程
函数式编程初探
最近初学swift,和OC比,发现语言更现代,也有了更多的特性。如何写好swift代码,也许,熟练使用新特性写出更优秀的代码,就是答案。今天先从大的方向谈谈swift中的编程范式-函数式编程。主要还是读了大佬帖子,写写自己的理解。
什么是函数式编程
"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。
它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
它使代码更像自然语言,告诉程序员要干什么,而不是怎么干,把怎么干的细节拆分到各个函数中。调用的地方逻辑清晰,便于debug。
举例来说,现在有这样一个数学表达式:
- ( + ) * -
传统的过程式编程,可能这样写:
- var a = + ;
- var b = a * ;
- var c = b - ;
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
- var result = subtract(multiply(add(,), ), );
这就是函数式编程。
为什么使用函数式编程
代码简洁,开发快速
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
接近自然语言,易于理解
函数式编程的自由度很高,可以写出很接近自然语言的代码。我们把程序的逻辑分成了几个函数,这样一来,我们的代码逻辑也会变得几个小碎片,于是我们读代码时要考虑的上下文就少了很多,阅读代码也会更容易。 而把代码逻辑封装成了函数后,我们就相当于给每个相对独立的程序逻辑取了个名字,于是代码成了自解释的。
前文曾经将表达式(1 + 2) * 3 - 4,写成函数式语言:
- subtract(multiply(add(,), ), )
对它进行变形,不难得到另一种写法:
- add(,).multiply().subtract()
更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
易于"并发编程"
函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
函数式编程的一些重要概念
函数是"第一等公民"
所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。
- var print = function(i){ console.log(i);};
- [,,].forEach(print);
函数(function)和procedure是有区别的
函数(function)这个名词来自于数学,函数通过一个给定的值,计算出另外一个值,也就是上学时常见的f(x)。
在通常的理解中,下面代码里面,f1,f2,f3通常都被叫做函数:
- //伪代码 函数无入参,返回2
- def f1(): return
- //函数有参数x,返回 x+1
- def f2(int x): return x+
- //函数无入参,无返回值,打印hello world
- def f3(): print("hello world")
复制代码但实际上,函数(function)和procedure是有区别的: function 通过运算返回一个值,而procedure只执行一段代码,没有返回值。 这一点对于后面的理解是非常有帮助的,首先要区分出二者。
再回到上面的代码中,f1,f2,为function而f3为procedure。
Pure Function(纯函数)
Pure:纯的; 单纯的; 纯真的; 干净的 我们将满足下面两个条件的函数称作Pure Function:
- 函数不会产生side effect(no side effect)
函数满足referential transparency这个条件 (原谅我不会翻译这两个名词)
Side effect
函数调用后不会对外部状态产生影响,比如下面这段代码中sum函数是no side effect的:
- def sum(a,b): return a+b
产生side effect的函数长成什么样呢?其实经常会写这样的函数:
- int sum =
- def plus(a,b){
- sum = a + b
- return sum
- }
plus函数除了计算参数之和以外,还改变了外部变量sum的值,我们plus这个函数产生了side effect。 常见的Side effect
- 改变外部变量的值(上面的例子中plus函数)
- 像磁盘中写入数据
将页面上的一个按钮设置为可点击,或者不可点击
前面提到function和procedure的不同点,在side effect这个角度来讲,pure funcion不会产生side effect,procedure通常会产生side effect。
满足Referential Transparency的函数可以将可以将用函数计算的结果替换表达式本身,而不影响程序的逻辑。 给定指定的参数,在任何时候返回的值都是相同的。不受其他外部条件影响。
两者说的意思是一样的,只是表达的角度是不同的 举个满足RT的例子
- def f(): return
- print(f() + f())
- print()
下面这段代码中的f()是满足RT的函数,按照上面的解释,我们可以将f()的结果也就是2替换掉f(),不会影响程序本身的逻辑:
- def f(): return
- print( + f())
- print()
或者这样替换:
- def f(): return
- print(f() + )
- print()
从另一个角度说,f()这个函数无论在什么时候调用,返回的值都是一样的,不会发生改变(没有外部条件影响)
举个不满足RT的例子
- int counter =
- def f(x){
- counter +=
- return x + counter
- }
这个例子中,f(x)这个函数不满足RT 下面的代码中,当我们用f(1)的计算结果一次替换代码中f(1)本身时,程序的逻辑是错误的:
- //原始的代码执行结果是:3
- f() + f()
- //把f(1)的结果1替换进来,下面函数执行的结果是:2
- f() +
- //同样,得到2
- + f()
- //得到2
- +
我们不能用执行的结果替换函数本身, 换个角度,下面两行代码执行的结果也不同
- f() + f()
- * f()
虽然入参都为1,但f(1)在不同时候调用得到的结果不同,因此f不满足RT这个条件
回到pure function,理解了side effect 和 referential transparency的含义,我们再来重温pure function的定义,就很好理解了:
No side effect
Referential transparency
满足这两个条件的函数,称之为pure function
引用透明
引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。
swift中函数式编程的应用
高阶函数
先说两个概念型的名词:
高阶函数(high order func),指可以将其他函数作为参数或者返回结果的函数。
一级函数(first class func),指可以出现在任何其他构件(比如变量)地方的函数。
map
- map { (Element) -> Element in
- 对 element 进行处理
- }
一般用在集合类型,对集合里的元素进行遍历,函数体里实现对每一个元素的操作。
- var arr = [,,,]
- let mapres = arr.map {
- "NO." + String($)
- }
// 运行结果:["NO.1", "NO.3", "NO.2", "NO.4"]
reduce
- reduce(Result) { (Result, Element) -> Result in
- 基于 Result 对当前的 Element 进行操作,并返回新的 Result
- }
一般用在集合类型,对集合里的元素进行叠加处理,函数体里传两个参数,第一个是之前的叠加结果,第二个是当前元素,返回值是对当前元素叠加后的结果。
- // 对数组里的元素:奇数相加,偶数相乘
- var arr = [,,,]
- let reduceRes = arr.reduce((,)) { (a:(Int,Int), t:Int) -> (Int,Int) in
- if t % == {
- return (a. + t, a.)
- } else {
- return (a., a. * t)
- }
- }
- // 运行结果:(4,8)
filter
- filter { (Element) -> Bool
- 对元素的筛选条件,返回 Bool
- }
一般用在集合类型,对集合里的元素进行筛选。函数体里实现筛选条件,返回 true 的元素通过筛选。
- var arr = [,,,]
- let filterRes = arr.filter {
- $ % ==
- }
- // 运行结果:[2,4]
flatMap
首先先看下 Swift 源码里对集合数组的map和flatmap的实现:
- // Sequence.swift
- extension Sequence {
- public func map<T>(_ transform: (Element) -> T) -> [T] {}
- }
- // SequenceAlgorithms.swift.gyb
- extension Sequence {
- public func flatMap<T>(_ transform: (Element) -> T?) -> [T] {}
- public func flatMap<S : Sequence>(_ transform: (Element) -> S) -> [S.Element] {}
- }
前面我们已经知道,map是一种遍历,而上面的代码又显示出来,flatmap有两种重载的函数:
其中一种与map非常相似,差别只在闭包里的返回值变成了可选类型。 另一种稍微有点不同,闭包传入的是数组,最后返回的是数组的元素组成的集合。
- // map
- let arr = [,,nil,,nil,]
- let arrRes = arr.map { $ } // 结果为:[Optional(1), Optional(2), nil, Optional(4), nil, Optional(5)]
- // flatmap
- let brr = [,,nil,,nil,]
- let brrRes = brr.flatmap { $ } // 结果为:[1, 2, 4, 5]
- let crr = [[,,],[,,]]
- let ccRes = crr.flatmap { $ } // 结果为:[1, 2, 4, 5, 3, 2]
- let cdRes = crr.flatmap { c in
- c.map { $ * $ }
- } // 结果为[1, 4, 16, 25, 9, 4]
- // 使用 map 实现的上面平铺功能
- let ceRes = Array(crr.map{ $ }.joined()) // 同 ccRes
- let cfRes = Array(crr.map{ $ }.joined()).map{ $ * $ } // 同 cdRes
简单理解为,flatMap可以将多维数组平铺,也还以过滤掉一维数组中的nil元素。
map和flatMap不只在数组中可以使用,对于 Optional 类型也是可以进行操作的。先看下面这个例子:
- let a: Date? = Date()
- let formatter = DateFormatter()
- formatter.dateStyle = .medium
- let c = a.map(formatter.string(from:))
- let d = a == nil ? nil : formatter.string(from: a!)
- c 和 d 是两种不同的写法,c 写法是不是更优雅一些?
参考文章:
swift之函数式编程的更多相关文章
- swift 之函数式编程(一)
1. 什么是函数式编程? 函数式编程是阿隆佐思想的在现实世界中的实现, 它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及异变物件. 函数式编程的最重要基础是λ演算.而且λ演算的函數可以接受函 ...
- swift之函数式编程(四)
文章内容来自<Functional Programing in Swift>,具体内容请到书中查阅 Map, Filter, Reduce Functions that take func ...
- swift之函数式编程(二)
本文的主要内容来自<Functional Programming in Swift>这本书,有点所谓的观后总结 在本书的Introduction章中: we will try to foc ...
- 最通俗易懂的方式让你理解 Swift 的函数式编程
函数式编程(Functional Programming)是相对于我们常用的面向对象和面向过程编程的另外一种开发思维方式,它更加强调以函数为中心.善用函数式编程思路,可以对我们的开发工作有很大的帮助和 ...
- swift之函数式编程(三)
文章来源于<Functional Programing in Swift>,本系列仅仅是观后概括的一些内容 Wrapping Core Image 上一篇文章我们介绍了 高阶函数并且展示了 ...
- swift之函数式编程(五)
文章内容来源于<Functional Programing in Swift>,详情请看原著 The Value of Immutability swift 对于控制值改变有一些机制.在这 ...
- Swift の 函数式编程
Swift 相比原先的 Objective-C 最重要的优点之一,就是对函数式编程提供了更好的支持. Swift 提供了更多的语法糖和一些新特性来增强函数式编程的能力,本文就在这方面进行一些讨论. S ...
- swift语言的特征:类型系统与函数式编程:swift是面向类型和面向函数编程的语言
swift语言的特征: 类型系统:值类型与引用类型.泛型.协议类型 函数式编程:
- 函数式编程-将Monad(单子)融入Swift
前言 近期又开始折腾起Haskell,掉进这个深坑恐怕很难再爬上来了.在不断深入了解Haskell的各种概念以及使用它们去解决实际问题的时候,我会试想着将这些概念移植到Swift中.函数式编程范式的很 ...
随机推荐
- 单片机pwm控制基本原理详解
前言 PWM是Pulse Width Modulation的缩写,它的中文名字是脉冲宽度调制,一种说法是它利用微处理器的数字输出来对模拟电路进行控制的一种有效的技术,其实就是使用数字信号达到一个模拟信 ...
- Java复习总结——详细理解Java反射机制
反射是什么 反射的作用用一句简单的话来讲就是可以对代码进行操作的代码,这个特性经常在被用于创建JavaBean中,通常造轮子的人会用到这个特性,而应用程序员用到这个特性的场景则较少. 能够分析类能力的 ...
- iview 模态框点击确定按钮不消失
<div slot="footer"> <Button type="text" size="large" @click=& ...
- bzoj4566 找相同字符
题意:给定两个字符串,从中各取一个子串使之相同,有多少种取法.允许本质相同. 解:建立广义后缀自动机,对于每个串,分别统计cnt,之后每个点的cnt乘起来.记得开long long #include ...
- 收藏:H.264编码原理以及I帧B帧P帧
来源: https://www.cnblogs.com/herenzhiming/articles/5106178.html 前言 ----------------------- H264是新一代的编 ...
- NOIP 普及组 2012 寻宝(思维???)
传送门 https://www.cnblogs.com/violet-acmer/p/9937201.html 题解: 一开始用暴力查找下一个要去的房间,超时了,emmmmm....... 然后,就稍 ...
- AndroidS软件代码提示
在用Eclipse时候,你可以进行设置,设置成不管你输入任何字母,都能进行代码的提示,在Android Studio中也可以 设置,而且比Eclipse设置来的简单.当然如果你觉得代码自动提示会降低你 ...
- Luogu P3975 [TJOI2015]弦论
题目链接 \(Click\) \(Here\) 题目大意: 重复子串不算的第\(k\)大子串 重复子串计入的第\(k\)大子串 写法:后缀自动机. 和\(OI\) \(Wiki\)上介绍的写法不太一样 ...
- SpringBoot项目部署在同一个tomcat容器报错
在一个Tomcat容器中部署了两个springboot的应用,在启动时发现一直都是第一个启动的项目能启动成功,第二个项目启动报错,错误信息如下: 2018-01-30 15:49:27.810 ERR ...
- InnoDB和MyISAM的区别
一.索引的实现 我们都知道InnoDB和MyISAM都是B+数的结构,但是它们的实现有点不一样,直接上图: 因此,MyISAM的查询性能会比InnoDB强 如果用InnoDB是必须有主键的,主键建议用 ...