真·函数式编程 学习函数式编程的学习笔记
- 禁用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)
就打印了下标为 0
的 arr
数组
那么 使用 loop_on_array([1, 2, 3, 4, 5])(item => console.log(item))(1)
就可以打印下标为 1
的 arr
数组了
(_ => 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
化参数:
-
step1
也就是第一次要执行的方法, 例子中的item => console.log(item)
-
step2
也就是递归调用的,_ => loop_on_array(arr)(body)(i + 1)
的写法也能看出他根本不在乎传入的参数, 他只是用来回调loop_on_array
的 所以使用了_
来忽略传入参数 -
params
在step1
中是当前的下标,+1
之后就会是step2
的parmas
// 这里的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/