初入函数式编程

真·函数式编程 学习函数式编程的学习笔记

  • 禁用var/let,所有东西都用const定义,也就是说无变量,强制immutable。
  • 禁用分号,也就是不让“顺序执行”,解除过程式编程范式
  • 禁用if/else,但允许用条件表达式condition ? expr1 : expr2。
  • 禁用for/while/do-while。
  • 禁用prototype和this来解除JS中的面向对象编程范式
  • 禁用function和return关键字,仅用lambda表达式来编程(JS里叫箭头函数)。
  • 禁用多参数函数,只允许使用单个参数,相当于强行curry化

根据作者的提示, 先自宫一刀!
啊啊啊~ 好疼~
不过蛮爽的, 嘿嘿嘿!

ES 基本的箭头函数啥的略过

实现循环

命令式:

for (var i = 0; i < arr.length; i++) {
    // ...
}

这个太常见了, 也太简单了,第一天学编程就是这个玩意儿吧.

题外话: 关于这个例子 var i = 0 部分在 javascript 这个 var 写了和没写没啥区别.
使用 ES2015 改成 let 吧!

好! 来直接看 函数式 怎么写:

const two_steps = step1 => step2 => param =>
  step2(step1(param))

const loop_on_array = arr => body => i =>
    i < arr.length
    ? two_steps(body)(_ => loop_on_array(arr)(body)(i + 1))(arr[i])
    : undefined

别灰心,我第一次看也看不懂.

看个过度版本的例子:

function loop_on_array(arr, body, i) {
    if (i < arr.length) {
        body(arr[i])
        loop_on_array(arr, body, i + 1)
    }
}

这个都能看懂, 就是回调自己, 递归嘛.

但是根据 之前的 禁令, 貌似写了的都违背了.

回到之前的函数式写法, 再复制一遍

const two_steps = step1 => step2 => param =>
  step2(step1(param))

const loop_on_array = arr => body => i =>
  i < arr.length
  ? two_steps (body)(_ => loop_on_array(arr)(body)(i + 1))(arr[i])
  : undefined

现在是不是稍微能理解一点了,
就是用三目运算符代替 if/else
写了个 two_step 用作递归.

嗯,没错 two_step 就是用来递归的, 看懂这里你就可以 × 掉本页面了!

再来看下如何使用这个函数式的循环

loop_on_array([1, 2, 3, 4, 5])(item => console.log(item))(0)

对的, 没错我们写了个蛋疼的 0 (暂时先别管这个)

咱们遍历这个数组的时候, 第一个curry参数放的数组, 第二个放的一个函数, 第三个是个蛋疼 0
好大家简单做个映射, 等下要用的.

arr  = [1,2,3,4,5]
body = [item => console.log]
i    = 0

啥是curry化? 简而言之就是多个参数变成一个参数的写法, 相关的lambda演算.

上面参数的顺序可以通过调整对应 curry 参数位置调整的, 因为函数式不是按顺序执行的

咱反过来推倒, 这个例子的效果其实很简单, 就是打印数组里的所有内容.

  • 0 必然是作为第一次使用的下标值
  • item => console.log(item) 接受的参数 item 也肯定是[1,2,3,4,5]里的单个元素
  • 怎么样使它回调自己呢? 那就是使用 two_steps 方法了

好看看在 loop_on_array 中是如何使用 two_steps

回想下之前的映射关系.

two_steps (body)(_ => loop_on_array(arr)(body)(i + 1))(arr[i])

那么上式中:

arr[i] = arr[0] = 1
body   = item => console.log(item)

啥都不管, 我貌似看到可以先打印出一个 1, 只要吧 arr[0] 当作变量传给 body 就可以了
打印完第一个, 我肯定要接着打第二个,
这么看来使用 loop_on_array([1, 2, 3, 4, 5])(item => console.log(item))(0)
就打印了下标为 0arr 数组
那么 使用 loop_on_array([1, 2, 3, 4, 5])(item => console.log(item))(1)
就可以打印下标为 1arr 数组了

(_ => loop_on_array(arr)(body)(i + 1)) 这里不就是这么用的吗.

等等, 在 loop_on_array 的三目表达式中 只要 i < arr.length 就会执行 two_steps
也就是说 这会递归调用自己.

这时候看 two_steps 就能看懂了吧.

const two_steps = step1 => step2 => param => step2(step1(param))

two_steps 接受3个 curry化参数:

  1. step1 也就是第一次要执行的方法, 例子中的 item => console.log(item)
  2. step2 也就是递归调用的, _ => loop_on_array(arr)(body)(i + 1)的写法也能看出他根本不在乎传入的参数, 他只是用来回调 loop_on_array的 所以使用了_来忽略传入参数
  3. paramsstep1 中是当前的下标, +1 之后就会是step2parmas
// 这里的3个参数顺序调换对应的只要吧 `step2(step1(param))` 就可以了
// 比如我写成 `const two_steps = step1 => param => step2 => step2(step1(param))`
? two_steps(body)(_ => loop_on_array(arr)(body)(i + 1))(arr[i]) 
// 改成
? two_steps(body)(arr[i])(_ => loop_on_array(arr)(body)(i + 1))
// (body)(arr[i])这样放近一点我更容易理解 arr[i] 是作为 body 的参数
// 然后再回调 (_ => loop_on_array(arr)(body)(i + 1)) 这个方法

刚了解函数式可能觉得, OMG 写个破循环这么费劲.
是的, 我也是这么认为的...
那是因为咱们还没见识到她的魅力~

关于这个蛋疼的0其实很好解决:

const new_loop = arr => body => loop_on_array(arr)(body)(0)

能看出一点他的魅力吗?

前端正在革命, 我们不要做切图仔!

此文写给不愿意做切图仔的你我.
参考: http://web.jobbole.com/84882/

你可能感兴趣的:(初入函数式编程)