内容简介:最近两周利用空余时间《函数式Swift》
最近两周利用空余时间 艰难 “啃完”了objc.io出版的 《函数式Swift》 这本书,感觉有些摸到了函数式编程的门道;在函数式编程 思维 的影响下,将之前的项目代码进行了改造。关于函数式编程,也算是有了一点心得,遂写成此文,虽然本文主要是以Swift为 载体 举例,但并不影响函数式思想的介绍。由于本人才疏学浅,而函数式编程本身 博大精深 ,故谬误在所难免,如发现,还请指出。
《函数式Swift》
函数式编程
WHAT is 函数式编程
wiki对于函数式编程的定义如下:
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. It is a declarative programming paradigm, which means programming is done with expressions or declarations instead of statements.
我认为最重要的两个单词是 programming Paradigm 和 declarative ,而后者又是为了描述前者准备的。什么是programming Paradigm?我在前几个月写的博客《面向协议编程初探》中已经解释过了,中文可以翻译为 编程范式 ,我理解为 编程语言设计者希望编程语言的使用者在使用编程语言的时候,思考问题的方式。
而declarative可以翻译为 声明式 的,与之相对应的是 imperative (指令式的),目前最为广泛使用的 面向对象编程 就可以划到指令式编程这一类。
函数式编程的和面向对象编程的历史
在函数式编程面前,面向对象编程其实是晚辈。如果以smalltalk的出现作为面向对象编程元年,那么面向对象编程的历史应该从1975年算起(数据来源 百度百科 );而函数式编程的元年可以追溯到Lisp语言出现的1958年(数据来源 百度百科 )。
函数式编程被很多大佬美誉为 the next big thing ,甚至被称为 最好 的编程范式。让人不免疑惑,既然比面向对象编程出来的早,为什么之前没火,而现在又火了呢?
WHY函数式编程这两年又火了
带着疑问,我在搜索引擎中搜索了“函数式编程”和“火了”这两个关键词,找到了一个 知乎问答 ,了解了这一段历史。
很赞成知乎用户 狗好看 的回答:
根本的原因是 摩尔定律 不适用。cpu的性能提升将体现在 核数 增加,这样并行的程序运行速度会越来越快。并行的程序的写法就是找出不能并行的地方,其他地方都尽量并行。如果要这样写,最需要避免的事情就是赋值。函数式编程的本质就是,规避掉“赋值”。
他的回答比较不容易懂,我来用我的理解 翻译翻译 。
摩尔定律: 当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。
越来越多的人知道,放在初期还是成立的摩尔定律,最近有点不适用了。这和其他很多学科一样,开始的指数级发展,很容易让人过于乐观,到了瓶颈期后,学科发展很容易停滞不前。一个最明显的例子是医学领域关于癌症的笑话,说癌症被攻克, 永远还需要30年 。我用的第一台电脑的CPU是 奔腾4 的,同期经常听到的词还有 赛扬 ,奔四有1.4GHz左右的内核时钟,到今天我用的是2016年的顶配MacBook Pro,查了一下,参数为2.7GHz( 约20年过去了,还不到2000年的两倍 )。
2016年的顶配MacBook Pro
Intel的工程师也尝试过将这一参数加到3GHz甚至更高,但是他们发现功耗太高、发热太快。但是每年产品线又要更新,那怎么办呢?只能是往CPU里 塞核心 来获得 计算能力 和 吞吐量 ,刚刚发布的MacBook Pro 2018顶配已经用上了 多达8核心的i9 处理器,甚至连iPhone X都已经有了6个核心。将这些核心都利用上,可以让设备充分发挥作用,在面向对象编程中,经常用到的技术是 同步机制和加锁 ,但由于函数式编程的特性,在函数式编程的世界里就不会出现这个问题,因此函数式编程又 火了 。
其实在科技界这种死灰复燃的例子还有很多,zelear的 王自如 在他的节目《科技相对论》 小众产品复活指南 里曾经介绍过几款死灰复燃产品,比如: 有轨电车、死飞、机械键盘、拍立得、车载广播 等。这些产品和函数式编程一样,都没有被 替代 的那么彻底,时过境迁,找到了合适的土壤,用户突然又开始想念他的某个功能,所以又活过来了。
函数式编程的特性(HOW)
很遗憾,经过上一节的解释,我依然未能说清函数式编程是什么。这个问题跟面向对象等其他编程范式一样很难给出准确的定义,只能从几个比较 热门 的特性列举一些例子。
一等函数
函数的重要性在函数式编程里不言而喻,在支持函数式编程特性的语言里,函数被 提到 了一个非常重要的位置,他跟 Int、String、Bool 有着相同的地位。函数可以作为变量的字面量存储起来、作为函数的参数和返回值在函数之间传递。
函数作为变量的例子很多,只想说一句话:
闭包是函数的 字面量 ,就像1之于Int,true之于Bool。
Currying
接下来举一个函数作为返回值的例子:
func addFactory(value1 : Int) -> (Int -> Int){ func adder(value2 : Int) -> Int { return value1+value2 } return adder } let addOne = addFactory(1) addOne(2) // return 3 addFactory(1)(2) // return 3 func add(value1 : Int , value2 : Int){ return value1 + value2 }
addFactory返回的值是一个函数,其类型为(Int -> Int),意思是返回值是一个接受Int并且返回Int的函数,我们可以用两种方式调用它。其中第二种addFactory(1)(2)又被称为我们习以为常的add(1,2)这种函数调用方式的 Currying(柯里化) 。多说一句,Currying是一个人的名字,他的全名叫 Haskell Currying,剩下的应该不需要多解释了。
高阶函数
接受其它函数作为参数的函数有时被称为 高阶函数 。或许大多数人都使用过 Map/Reduce/Filter ,他们就是高阶函数最常见的例子。
let nums = [1,4,3,5] // way 1 let strs : [String] = [] for i in (0..<nums.count) { strs[i] = "No." + String(nums[i]) } // way 2 let brr = nums.map { "No." + String($0) }
以Map为例,假设我们有这样一个需求,将一个整型数组,变为一个String类型的数组,并且在每个数字前加上“No.”,一个没有函数式编程思想的 程序员 极可能写出way1的代码,这种循环的代码几乎每个程序员都写了几千遍了,很好懂也并没有觉得有什么异常,但其实这是一种 指令式 的编程方式。何为指令式编程呢?就是人以机器的思维方式去思考,我们把自己当做了一台机器,比如上面way1的实现方式,就是我们将思维映射到了CPU上,强迫自己像CPU一样去思考。机器是怎么处理这个问题的呢?他首先要开辟一片内存,然后变更寄存器的值映射到变量i上,通过递增来做循环,然后创建字符串的字面量放到刚开辟出来的内存指定位置上。
way2使用了集合类型的高阶函数,它接收一个参数,这个参数是另一个函数(函数名不重要),负责String的初始化的方法。当它拿到这个函数之后,自动帮我们把里面的每一个元素拿出来,传到这个String的初始化函数里面去,就生成了最终的数组。这就是 声明式编程 ,好处很明显,代码比以前短了很多,思维方式变得更像人思考的方式了。
有了高阶函数,函数可以自由 装配 ,由一些简单的函数 装配 出一些高级的函数。
装配过程的可视化
纯函数(pure function)
在函数式编程中,对于函数还有两点特殊的要求。
- 不依赖外部
- 不改变外部
满足上面两点要求的函数被称为 纯函数(pure function) 。这两点保证了无论在什么时候调用函数,对于相同的输入,总会得到相同的输出。这至少带来了两点好处: - 函数的可测试性
- 上文提到的函数式编程没有的 同步与加锁 问题
至此也可以引出函数式编程的 思想 了:
避免使用程序状态和可变对象,是降低程序复杂度的有效方式之一,而这也正是函数式编程的精髓。函数式编程强调执行的结果,而非执行的过程。我们先构建一系列简单却具有一定功能的小函数,然后再将这些函数进行组装以实现完整的逻辑和复杂的运算,这是函数式编程的基本思想。
函子、适用函子、单子(Functor, Applicative, Monad)
这个部分需要单独写一篇文章介绍,我在理解过程中发现了一个很好的图解blog.
这里先给出结论:
functor: 通过 fmap 或者 <$> 应用是函数到封装过的值
applicative: 通过 <*> 或者 liftA 应用封装过的函数到封装过的值
monads: 通过 >>= 或者 liftM 应用会返回封装过的值的函数到封装过的值
以上所述就是小编给大家介绍的《浅谈函数式编程<一> (Introducing Functional Programming)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 函数式编程之数组的函数式编程
- 函数式编程 – 函数式编程如何影响您的编码风格?
- 纯函数:函数式编程入门
- 深入理解 Java 函数式编程,第 1 部分: 函数式编程思想概论
- 思想交融,Android中的函数式编程(2):什么是函数式编程
- 编程范式 —— 函数式编程入门
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。