浅谈函数式编程<一> (Introducing Functional Programming)

栏目: 编程语言 · 发布时间: 6年前

内容简介:最近两周利用空余时间《函数式Swift》

最近两周利用空余时间 艰难 “啃完”了objc.io出版的 《函数式Swift》 这本书,感觉有些摸到了函数式编程的门道;在函数式编程 思维 的影响下,将之前的项目代码进行了改造。关于函数式编程,也算是有了一点心得,遂写成此文,虽然本文主要是以Swift为 载体 举例,但并不影响函数式思想的介绍。由于本人才疏学浅,而函数式编程本身 博大精深 ,故谬误在所难免,如发现,还请指出。

浅谈函数式编程<一> (Introducing Functional Programming)

《函数式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 Paradigmdeclarative ,而后者又是为了描述前者准备的。什么是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年的两倍 )。

浅谈函数式编程<一> (Introducing Functional Programming)

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的初始化函数里面去,就生成了最终的数组。这就是 声明式编程 ,好处很明显,代码比以前短了很多,思维方式变得更像人思考的方式了。

有了高阶函数,函数可以自由 装配 ,由一些简单的函数 装配 出一些高级的函数。

浅谈函数式编程<一> (Introducing Functional Programming)

装配过程的可视化

纯函数(pure function)

在函数式编程中,对于函数还有两点特殊的要求。

  1. 不依赖外部
  2. 不改变外部
    满足上面两点要求的函数被称为 纯函数(pure function) 。这两点保证了无论在什么时候调用函数,对于相同的输入,总会得到相同的输出。这至少带来了两点好处:
  3. 函数的可测试性
  4. 上文提到的函数式编程没有的 同步与加锁 问题

至此也可以引出函数式编程的 思想 了:

避免使用程序状态和可变对象,是降低程序复杂度的有效方式之一,而这也正是函数式编程的精髓。函数式编程强调执行的结果,而非执行的过程。我们先构建一系列简单却具有一定功能的小函数,然后再将这些函数进行组装以实现完整的逻辑和复杂的运算,这是函数式编程的基本思想。

函子、适用函子、单子(Functor, Applicative, Monad)

这个部分需要单独写一篇文章介绍,我在理解过程中发现了一个很好的图解blog.

浅谈函数式编程<一> (Introducing Functional Programming)

这里先给出结论:

functor: 通过 fmap 或者 <$> 应用是函数到封装过的值

applicative: 通过 <*> 或者 liftA 应用封装过的函数到封装过的值

monads: 通过 >>= 或者 liftM 应用会返回封装过的值的函数到封装过的值


以上所述就是小编给大家介绍的《浅谈函数式编程<一> (Introducing Functional Programming)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Computational Geometry

Computational Geometry

Mark de Berg、Otfried Cheong、Marc van Kreveld、Mark Overmars / Springer / 2008-4-16 / USD 49.95

This well-accepted introduction to computational geometry is a textbook for high-level undergraduate and low-level graduate courses. The focus is on algorithms and hence the book is well suited for st......一起来看看 《Computational Geometry》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码