1 深坑
函数式编程, 最近貌似火了起来, 带跑了一大堆不明所以的吃瓜群众, 涌入了一个以 Haskell 为代表的深坑, 就连守旧的 Java 在 Java8中也加入了 lambda 表达式, 于是乎, 各种Monad
, Functor
, Applicative
等等高大上的名词接踵而至, 搞得人怀疑自己智商是不是已经不适合这个版本了...
2 当然, 这些人不仅仅是为了扯淡!
那么问题来了, 这些东西完全没用吗? 只能说存在即合理, 这群高智商的 PhD 搞出来的东西不仅仅是为了扯淡(还可以发Paper...).
函数式编程的优点呢?
嗯! 翻开百度百科
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 更方便的代码管理
- 易于"并发编程"
- 代码的热升级
...
我 * ! 这么多好处!
代码简洁? 我还学个毛线 Java!
并发编程? 我还学个毛线 Java!
代码热更新? 我还...
撸起袖子就是干!
3 组合(嗯, 黑完 Java 舒服多了)
函数是数学中的概念, 高中数学中, 我们都学过 y = f(x)
, 自变量x
作为输入, y
是函数对应的值, 其本质就是从一系列 x 到 y 的映射, 但函数有一个条件, 对于任意一个 x, 都有一个确定唯一的值与其对应, 换句话说就是, 允许多对一, 不允许一对多, 牢记这点相当重要.
举个例子:
比如如下函数
f(x) = x * 2
将 x 的值乘 2 后返回
g(x) = x + 2
将 x 的值 加 2 后返回
如果我们有如下需求
- 先要加上2 再 乘 2
p(x) = f(g(x)) = 2 * (x + 2)
- 先乘上2 再加 2
p(x) = g(f(x)) = 2 + (x * 2)
- 在 2 的基础上再 乘上 2
p(x) = f(g(f(x)))
我们使用 f(x) 和 g(x) 的组合可以生成很多不同表现形式的函数
如果我们有
函数doClean(room)
代表打扫了房间
函数doTwice(someThing)
代表重复做某件事两次
那么 doTwice(doClean(room))就可以代表重复打扫房间两次
假如我们有无限多各种各样的简单函数, 我们是不是能通过组合构建出全世界!
4. 无副作用
数学中的函数还有一个性质, 对于一个固定的输入, 一定会有一个固定的输出, 不管这个函数执行过多少次
比如:
上面例子中, 我们通过不同组合构建出许多不同性质的函数, 但对于某一固定的组合, 如果自变量 x 确定, 那么结果也一定是确定的
p(x) = f(g(f(x))) = 2 * (2 + ( x * 2))
当 x = 1时, p(1) == 8 永远成立
注意, 数学中的函数式无副作用的, 但常见编程语言中的函数大多是有副作用的, 比如一个
doClean()
函数更改了函数外部的一些变量等等, 来表明做了 clean 这件事
4. 抽象
'抽象' 这两个字本来就很抽象...
很多同学对这个词本来就觉得很模糊, WTF! 到底什么是抽象
函数式编程中, 抽象代表着一些列相似动作的总结和归纳
举个例子:
日常生活中, 去日本, 去上海, 去爬山,等一些动作,都有相似性, 我们都可以抽象出来一个公共的动作'去'
所以, 我们就可以总结(抽象)出来
去(日本)
去(上海)
去(爬山)
...
在代码中也是一样的, 我们需要在控制台上打印这个动作被抽象成了print
函数, 用来打印
有些同学可能接触过函数式编程中的几个能够装逼的函数map
, filter
等, 在日常代码中用上几个 map
和filter
似乎能让自己的精神升华到另外一个新高度,还可以顺便鄙视下那些习惯用 for
循环的同学(哈哈)
其实map
就是对 for
循环的抽象
// 这段代码就是将数组中的元素加上1之后返回
// // 输出 [2, 3, 4]
for x in [1, 2, 3] {
var ret = []
ret.append(x + 1)
return ret
}
// 这段代码就是将数组中的元素乘5之后返回
// 输出 [5, 10, 15]
for x in [1, 2, 3] {
var ret = []
ret.append(x * 5)
return ret
}
怎么抽象, 其实就是把相似的公共部分提取出来
func map<T, U>(f: (T) -> U, arr: [T]) -> [U] {
var ret = []
for x in arr {
ret.append(f(x))
}
return ret;
}
// 上面两个例子变成了
map({ $0 + 1 }) // 可以理解成第0个参数 + 1 之后返回
map({ $0 * 5 })
filter
呢?
// 如果符合条件就加进数组返回
func filter<T>(p: (T) -> Bool, arr: [T]) -> [T] {
var ret = []
for x in arr {
if (p(x)) {
ret.append(x)
} else {
continue
}
}
return ret
}
使用这些抽象, 我们可以让我们的代码更易读, 更不易出错
// 被4整除乘2 后能被5整除的第二个整数
integers.filter({ $0 % 4 == 0 }).map({ $0 * 2 }).filter({ $0 % 5 == 0 }).second()
使用 for 循环
// 第一眼你能看出来这段代码是在干嘛吗?
for x in integes {
var ret = []
if ret.count == 2 {
return ret[1]
}
if (x % 4 == 0 && (x * 2) % 5 == 0) {
ret.append(x)
}
}
5. 敲黑板:
- 通过组合简单的函数来构建出复杂的系统, 函数式编程的精髓就在于通过简单的函数来组合成复杂的函数
- 对于固定输入输出固定值, 这种性质也可以称作无副作用, 也就是说在函数内部过程不会受函数外部的影响, 也不会影响到函数外部
- 抽象是很强大的工具, 它能够让我们简化很多代码, 让代码更简洁, 便于理解, 而且更不易出错
- 然而工作中你还是应该继续用 Java, 函数式编程可激发你的脑洞, 但请避免在此越陷越深