上一篇文章使用JavaScript实现“真·函数式编程”本来以为可以一次性写完的,结果话痨本色,没办法,继续填坑,这篇应该可以完结了,讲道理嘛。
这篇当中将介绍如何在纯函数式的限制之下实现“局部变量”和“状态”。
2. 实现局部变量
首先考虑这样一段代码
1 2 3 4 5 6 7 8 |
function getName() { return 'name from somewhere' } function greeting(word) { var name = getName() return word + ', ' + name } console.log(greeting('Hello')) |
这是一段典型的命令式编程的代码,它最主要的问题就是局部变量name
。
在上一篇文章的第一个实现对数组遍历的例子当中我们已经对“顺序执行”初窥门径,通过构造了一个two_steps
函数,实现两个步骤(函数)顺序执行。
在这个构造过程当中,我们得到一个重要的思路,就是在函数式语言当中,如果你想“获得”一个什么东西,就构造一个新的函数,把它参数化。对于two_steps
的例子而言,“想获得”的是一个step2
,就把它参数化了。
所以当需要“获得”局部变量的时候,自然而然我们会想到,把要拿的东西参数化就OK了,于是我们可以简单的这么构造:
1 2 |
// local :: a -> (a -> b) -> b const local = a => f => f(a) |
local
函数接收两个参数,a
是要“捕获”的值,f
是接收或者说消费这个值的函数,用它来改造上面的代码
1 2 3 |
function greeting(word) { return local(getName())(name => word + ', ' + name) } |
上文当中将getName()
的结果“捕获”作为后面函数的参数,实现了“局部变量”name
。把上面的函数按照“真·函数式编程”规则改写:
1 2 3 4 5 |
const getName = () => 'name from somewhere' const greeting = word => local(getName())(name => word + ', ' + name) console.log(greeting('Hello')) // 结果是'Hello, name from somewhere' |
不难发现我们这个local
其实就是two_steps
的简化版,区别在于two_steps
的第一个step1
是一个函数,而local
则是一个值,如果用two_steps
实现local
那么就是:
1 |
const local = value => step2 => two_steps (_ => value) (step2) () |
看,我们这个local
的风格,看起来非常像JS当中的“回调”的方式——事实上,因为像Haskell这样的纯函数式语言没有顺序执行,你可以认为每一行代码执行顺序是不一定的,这非常类似于在JS中我们遇到了海量异步操作的时候:异步操作的执行顺序是不一定的,所以才会用回调函数来保证“异步操作->处理结果”这个顺序。回调是一种非常朴素,非常好理解,但写起来却反人类的异步编程方式。我一直不批判浏览器和node.js里把API都用回调风格来定义,因为它很原始,大家都懂,至于后来的如Promise
这些方式,也可以用回调的API轻松封装出来,咸甜酸辣,五仁叉烧,任君挑选。
OK,扯远了,也许你觉得上面的例子太过简单,下面我们来看这篇文章中真正重点的内容。
3. 实现状态
以下的例子基本上都源自从陈年译稿——一个面向Scheme程序员的monad介绍文中搬运和改造,我从这篇文章获得了巨大的启发,也先对作者和译者表示感谢。
我们写程序的过程当中常常回用到自增ID这种东西,如果在JS里要实现一个自增ID,可能会这么写
1 2 3 4 |
var __guid = 0 function guid() { return __guid++ } |
好嘛,绕了一圈,又回到刚才的话题了,局部变量(这次在闭包里面而已,本质是一样的),和二次赋值。但是经过前文的启发很容易就能用参数化的方式来解决这个问题
1 2 3 4 |
function guid(oldValue) { var newValue = oldValue + 1 return [newValue, newValue] } |
也就是
上一篇文章使用JavaScript实现“真·函数式编程”本来以为可以一次性写完的,结果话痨本色,没办法,继续填坑,这篇应该可以完结了,讲道理嘛。
这篇当中将介绍如何在纯函数式的限制之下实现“局部变量”和“状态”。
2. 实现局部变量
首先考虑这样一段代码
1 2 3 4 5 6 7 8 |
function getName() { return 'name from somewhere' } function greeting(word) { var name = getName() return word + ', ' + name } console.log(greeting('Hello')) |
这是一段典型的命令式编程的代码,它最主要的问题就是局部变量name
。
在上一篇文章的第一个实现对数组遍历的例子当中我们已经对“顺序执行”初窥门径,通过构造了一个two_steps
函数,实现两个步骤(函数)顺序执行。
在这个构造过程当中,我们得到一个重要的思路,就是在函数式语言当中,如果你想“获得”一个什么东西,就构造一个新的函数,把它参数化。对于two_steps
的例子而言,“想获得”的是一个step2
,就把它参数化了。
所以当需要“获得”局部变量的时候,自然而然我们会想到,把要拿的东西参数化就OK了,于是我们可以简单的这么构造:
1 2 |
// local :: a -> (a -> b) -> b const local = a => f => f(a) |
local
函数接收两个参数,a
是要“捕获”的值,f
是接收或者说消费这个值的函数,用它来改造上面的代码
1 2 3 |
function greeting(word) { return local(getName())(name => word + ', ' + name) } |
上文当中将getName()
的结果“捕获”作为后面函数的参数,实现了“局部变量”name
。把上面的函数按照“真·函数式编程”规则改写:
1 2 3 4 5 |
const getName = () => 'name from somewhere' const greeting = word => local(getName())(name => word + ', ' + name) console.log(greeting('Hello')) // 结果是'Hello, name from somewhere' |
不难发现我们这个local
其实就是two_steps
的简化版,区别在于two_steps
的第一个step1
是一个函数,而local
则是一个值,如果用two_steps
实现local
那么就是:
1 |
const local = value => step2 => two_steps (_ => value) (step2) () |
看,我们这个local
的风格,看起来非常像JS当中的“回调”的方式——事实上,因为像Haskell这样的纯函数式语言没有顺序执行,你可以认为每一行代码执行顺序是不一定的,这非常类似于在JS中我们遇到了海量异步操作的时候:异步操作的执行顺序是不一定的,所以才会用回调函数来保证“异步操作->处理结果”这个顺序。回调是一种非常朴素,非常好理解,但写起来却反人类的异步编程方式。我一直不批判浏览器和node.js里把API都用回调风格来定义,因为它很原始,大家都懂,至于后来的如Promise
这些方式,也可以用回调的API轻松封装出来,咸甜酸辣,五仁叉烧,任君挑选。
OK,扯远了,也许你觉得上面的例子太过简单,下面我们来看这篇文章中真正重点的内容。
3. 实现状态
以下的例子基本上都源自从陈年译稿——一个面向Scheme程序员的monad介绍文中搬运和改造,我从这篇文章获得了巨大的启发,也先对作者和译者表示感谢。
我们写程序的过程当中常常回用到自增ID这种东西,如果在JS里要实现一个自增ID,可能会这么写
1 2 3 4 |
var __guid = 0 function guid() { return __guid++ } |
好嘛,绕了一圈,又回到刚才的话题了,局部变量(这次在闭包里面而已,本质是一样的),和二次赋值。但是经过前文的启发很容易就能用参数化的方式来解决这个问题
1 2 3 4 |
function guid(oldValue) { var newValue = oldValue + 1 return [newValue, newValue] } |
也就是