美文网首页
(1) 函数式编程

(1) 函数式编程

作者: ParkinWu | 来源:发表于2017-06-30 16:42 被阅读64次

    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, 函数式编程可激发你的脑洞, 但请避免在此越陷越深

    相关文章

      网友评论

          本文标题:(1) 函数式编程

          本文链接:https://www.haomeiwen.com/subject/fdzbcxtx.html