(1) 函数式编程

2017-06-30  本文已影响64人  ParkinWu

1 深坑

函数式编程, 最近貌似火了起来, 带跑了一大堆不明所以的吃瓜群众, 涌入了一个以 Haskell 为代表的深坑, 就连守旧的 Java 在 Java8中也加入了 lambda 表达式, 于是乎, 各种Monad, Functor, Applicative等等高大上的名词接踵而至, 搞得人怀疑自己智商是不是已经不适合这个版本了...

2 当然, 这些人不仅仅是为了扯淡!

那么问题来了, 这些东西完全没用吗? 只能说存在即合理, 这群高智商的 PhD 搞出来的东西不仅仅是为了扯淡(还可以发Paper...).
函数式编程的优点呢?

嗯! 翻开百度百科

  1. 代码简洁,开发快速
  2. 接近自然语言,易于理解
  3. 更方便的代码管理
  4. 易于"并发编程"
  5. 代码的热升级
    ...

我 * ! 这么多好处!
代码简洁? 我还学个毛线 Java!
并发编程? 我还学个毛线 Java!
代码热更新? 我还...

撸起袖子就是干!

3 组合(嗯, 黑完 Java 舒服多了)

函数是数学中的概念, 高中数学中, 我们都学过 y = f(x), 自变量x作为输入, y是函数对应的值, 其本质就是从一系列 x 到 y 的映射, 但函数有一个条件, 对于任意一个 x, 都有一个确定唯一的值与其对应, 换句话说就是, 允许多对一, 不允许一对多, 牢记这点相当重要.

举个例子:
比如如下函数
f(x) = x * 2 将 x 的值乘 2 后返回
g(x) = x + 2 将 x 的值 加 2 后返回

如果我们有如下需求

  1. 先要加上2 再 乘 2
    p(x) = f(g(x)) = 2 * (x + 2)
  2. 先乘上2 再加 2
    p(x) = g(f(x)) = 2 + (x * 2)
  3. 在 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等, 在日常代码中用上几个 mapfilter似乎能让自己的精神升华到另外一个新高度,还可以顺便鄙视下那些习惯用 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. 敲黑板:

  1. 通过组合简单的函数来构建出复杂的系统, 函数式编程的精髓就在于通过简单的函数来组合成复杂的函数
  2. 对于固定输入输出固定值, 这种性质也可以称作无副作用, 也就是说在函数内部过程不会受函数外部的影响, 也不会影响到函数外部
  3. 抽象是很强大的工具, 它能够让我们简化很多代码, 让代码更简洁, 便于理解, 而且更不易出错
  4. 然而工作中你还是应该继续用 Java, 函数式编程可激发你的脑洞, 但请避免在此越陷越深
上一篇下一篇

猜你喜欢

热点阅读