JS函数式编程【译】5.2 函子 (Functors)

函子(Functors)

态射是类型之间的映射;函子是范畴之间的映射。可以认为函子是这样一个函数,它从一个容器中取出值, 并将其加工,然后放到一个新的容器中。这个函数的第一个输入的参数是类型的态射,第二个输入的参数是容器。

函子的函数签名是这个样子
// myFunctor :: (a -> b) -> f a -> f b
意思是“给我一个传入a返回b的函数和一个包含a(一个或多个)的容器,我会返回一个包含b(一个或多个)的容器”

创建函子

要知道我们已经有了一个函子:map(),它攫取包含一些值的容器(数组),然后把一个函数作用于它。

[1, 4, 9].map(Math.sqrt); // Returns: [1, 2, 3]

然而我们要把它写成一个全局函数,而不是数组对象的方法。这样我们后面就可以写出简洁、安全的代码。

// map :: (a -> b) -> [a] -> [b]
var map = function(f, a) {
  return arr(a).map(func(f));
}

这个例子看起来像是个故意弄的封装,因为我们只是把map()函数换了个形式。但这有它的目的。 它为映射其它类型提供了一个模板。

// strmap :: (str -> str) -> str -> str
var strmap = function(f, s) {
  return str(s).split(‘‘).map(func(f)).join(‘‘);
}

数组和函子

数组是函数式JavaScript使用数据的最好的方式。

是否有一种简单的方法来创建已经分配了态射的函子?有,它叫做arrayOf。 当你传入一个以整数为参数、返回数组的态射时,你会得到一个以整数数组为参数返回数组的数组的态射。

它自己本身不是函子,但是它让我们能够用态射建立函子。

// arrayOf :: (a -> b) -> ([a] -> [b])
var arrayOf = function(f) {
  return function(a) {
    return map(func(f), arr(a));
  }
}

下面是如何用态射创建函子

var plusplusall = arrayOf(plusplus); // plusplus是函子
console.log( plusplusall([1,2,3]) ); // 返回[2,3,4]
console.log( plusplusall([1,‘2‘,3]) ); // 抛出错误

函数组合,重访(revisited)

函数也是一种我们能够用函子来创建的原始类型,这个函子叫做“fcompose”。我们对函子是这样定义的: 它从容器中取一个值,并对其应用一个函数。如果这个容器是一个函数,我们只需要调用它并获取里面的值。

我们已经知道了什么是函数组合,不过让我们来看看在范畴论驱动的环境里它们能做些什么。

函数组合就是结合(associative,中学数学中学到的“结合律”中的“结合”)。如果你的高中代数老师也像我这样的话那她只告诉了你函数组合的定律有什么,而没有没教你用它能做些什么。在实践中,组合就是结合律所能够做的。

(a × b) × c = a × (b × c)
(f g) h = f (g h)

f g ≠ g f

我们可以任意进行内部组合,无所谓怎样分组。交换律也没有什么可迷惑的。f g 不总等于 g f。比如说,一个句子的第一个单词被反转并不等同于一个被反转的句子的第一个单词。

总的来说意思就是哪个函数以什么样的顺序被执行是无所谓的,只要每个函数的输入来源于上一个函数的输出。不过,等等,如果右边的函数依赖于左边的函数,不就是只有一个固定的求值顺序吗?从左到右?是的,如果把它封装起来,我们就可以按照我们感觉合适的方式来控制它。这就使得在JavaScript中可以实现惰性求值。

(a × b) × c = a × (b × c)

(f g) h = f (g h)

我们来重写函数组合,不作为函数原型的扩展,而是作为一个单独的函数,这样我们就可以的到更多的功能。基本的形式是这样的:

var fcompose = function(f, g) {
  return function() {
    return f.call(this, g.apply(this, arguments));
  };
};

不过我们还得让它能接受任意数量的输入。

var fcompose = function() {
  // 首先确保所有的参数都是函数
  var funcs = arrayOf(func)(arguments);  //译注:这句有问题,见下面注释
  // 返回一个作用于所有函数的函数
  return function() {
    var argsOfFuncs = arguments;
    for (var i = funcs.length; i > 0; i -= 1) {
      argsOfFuncs  = [funcs[i].apply(this, args)];
    }
    return args[0];
  };
};

// 例:
var f = fcompose(negate, square, mult2, add1);
f(2); // 返回: -36

给原著勘误:如果你copy上面的代码执行的话现在肯定看到报错了,上面这段代码里的错误还真不少……

首先会得到一个错误:“Uncaught TypeError: Error: Array expected, something else given.”。 哪个数组没通过类型验证呢?是fcompose里的arguments。我在最新版本的chrome和火狐里得到arguments的字符串是[object Arguments], 而且arguments并没有继承Array,也就没有map之类的方法,所以这里需要先把arguments转换成数组,把fcompose函数体第一句改成这样就行:
var funcs = arrayOf(func)(Array.prototype.slice.call(arguments));

然后第二个错误,低级错误,argsOfFuncs和args是一个东西,统一成一个变量名就行了。比如说把argsOfFuncs都改成args吧。
顺便说一下这里的意思,首先把初始参数赋给args,然后遍历组合函数的数组,每执行一个函数就把返回值赋给args,
这样下一个函数就能把上一个函数的执行结果作为输入参数了。注意每次的返回值都放到了数组里,是为了符合apply的参数形式,
而最后返回时只要取args里的第一个(也是唯一一个)值就行了。

第三个错误,还是低级错误,遍历funcs的时候计数写成了length到1,而实际上我们需要length-1到0。
顺便说下为什么计数要从大到小呢?因为组合的函数要从右往左执行。

最后,上正确的代码:

var fcompose = function() {
  var funcs = arrayOf(func)(Array.prototype.slice.call(arguments));
  return function() {
    var args = arguments;
    for (var i = funcs.length-1; i >= 0; i -= 1) {
      args  = [funcs[i].apply(this, args)];
    }
    return args[0];
  };
};

现在我们封装好了这些函数并可以控制它们了。我们重写了组合函数使得每一个函数接受另一个函数作为输入, 存储起来,并同样返回一个对象。这里并不是接受一个数组作为输入处理它,而是对每一个操作返回一个新的数组, 我们可以在源头上让每一个元素接受一个数组,把所有操作合到一起执行(所有map、filter等等组合到一起), 最终把结果存到一个新数组里。这就是通过函数组合实现的惰性求值。这里我们没有理由重新造轮子, 许多库对于这个概念都有很好的实现,包括Lazy.js、Bacon.js以及wu.js等库。

利用这一不同模式的结果,我们可以做更多事情:异步迭代、异步事件处理、惰性求值甚至自动并行。

自动并行?在计算机科学界有一个词叫做:IMPOSSIBLE。但是这真的不可能吗? 摩尔定律的下一个飞跃没准是一个能够将我们的代码并行化的编译器,函数组合能做到吗? 不,这行不通。JavaScript引擎实现并行化并不是自动的,而是依靠精心设计的代码。 函数组合只是提供了切分成并行进程的机会。但是它本身已经足够酷了。

下一节 单子(Monads)

?? Functional Programming in Javascript 主目录第五章 范畴论

时间: 2024-08-22 18:49:02

JS函数式编程【译】5.2 函子 (Functors)的相关文章

js 函数式编程 浅谈

js 函数式编程 函数式的思想, 就是不断地用已有函数, 来组合出新的函数. 函数式编程具有五个鲜明的特点: 1. 函数是"第一等公民" 指的是函数与其他数据类型一样,处于平等地位 2. 只用"表达式",不用"语句" "表达式"(expression)是一个单纯的运算过程,总是有返回值: "语句"(statement)是执行某种操作,没有返回值. 3. 没有"副作用" 指的是函数内部与外

js函数式编程(1)-纯函数

我将写的第一个主题是js的函数式编程,这一系列都是mostly adequate guide这本书的读书总结.原书在gitbook上,有中文版.由于原作者性格活泼,书中夹杂很多俚语,并且行文洒脱.中文译版难免有时需要思量一番,既然读了就写出来,能方便别人最好,也请读者指正.正文如下. 如果一个函数是纯函数,那么其不依赖外部环境,并且不产生副作用. 1.不依赖外部环境,反例如下: const a1 = 10; const aFunc1 = () => { // 依赖外部变量 return a1;

JS函数式编程【译】5. 范畴论

?? Functional Programming in Javascript 主目录上一章 Javascript中实现函数式编程的技术 第五章 范畴论 托马斯·沃森(时任IBM董事长)说过一句著名的话,"我想全世界只有五台计算机的市场". 那是1948年,当时,每个人都认为计算机只会被用于两件事情:数学和工程. 即使是技术上最大胆的预想也不会认为有一天计算机能够把西班牙语翻译成英语, 或者模拟整个天气系统.在那时最快的计算机是IBM的SSEC,每秒能计算50次,显示终端要在15年后才

JS函数式编程【译】4.2 函数组合

?? Functional Programming in Javascript 主目录第四章 在Javascript中实现函数式编程的技术 函数组合 终于,我们到了函数组合. 在函数式编程中,我们希望一切都是函数,尤其希望是一元函数,如果可能的话.如果可以把所有的函数转换为一元函数, 将发生神奇的事情. 一元函数是只接受单个输入的函数.函数如果有多个输入就是多元的,不过我们一般把接受两个输入的叫二元函数, 把接受三个输入的叫三元函数. 有的函数接受的输入的数量并不确定,我们称它为可变的. 操作函

JS函数式编程【译】5.3 单子 (Monad)

单子是帮助你组合函数的工具. 像原始类型一样,单子是一种数据结构,它可以被当做装载让函子取东西的容器使用. 函子取出了数据,进行处理,然后放到一个新的单子中并将其返回. 我们将要关注三种单子: Maybes Promises Lenses 除了用于数组的map和函数的compose以外,我们还有三种函子(maybe.promise和lens). 这仅仅是另一些函子和单子. Maybe Maybe可以让我们优雅地使用有可能为空并且有默认值的数据.maybe是一个可以有值也可以没有值的变量,并且这对

JS函数式编程【译】4.在Javascript中实现函数式编程的技术

?? Functional Programming in Javascript 主目录上一章 建立函数式编程环境 第四章 在Javascript中实现函数式编程的技术 扶好你的帽子,我们现在要真正进入函数式的思想了. 这章我们继续下面的内容: 把所有的核心概念放到一个集中的范式里 探索函数式编程之美 一步步跟踪函数式模式相互交织的逻辑 我们将贯穿整章建立一个简单的应用做一些很酷的事情 你可能已经注意到,在上一章我们介绍Javascript的函数式库的时候引入了一些概念, 而不是在第二章<函数式编

JS函数式编程【译】4.1 部分函数应用和珂理化

?? Functional Programming in Javascript 主目录第四章 在Javascript中实现函数式编程的技术 部分函数应用和珂理化 许多语言支持可选参数,但是Javascript不支持.Javascript采用一种完全不同的模式,它任允许意数量的参数传给函数. 这就给一些有趣且非同寻常的设计模式留下了门路.函数可以全部或部分应用. 部分应用在Javascript中的处理方式是:给函数的一个或多个参数绑定上值,然后返回另一个函数接受剩余的未绑定参数. 同样,珂理化的处

JS函数式编程 3.1 Javascript的函数式库

?? Functional Programming in Javascript 主目录第三章 建立函数式编程环境 Javascript的函数式库 据说所有的函数式程序员都会写自己的函数库,函数式Javascript程序员也不例外. 随着如今开源代码分享平台如GitHab.Bower和NPM的涌现,对这些函数库进行分享.变得及补充变得越来越容易. 现在已经有很多Javascript的函数式变成苦,从小巧的工具集到庞大的模块库都有. 每一个库都宣扬着自己的函数式编程风格.从一本正经的数学风格到灵活松

[原创译书] JS函数式编程 2.3 函数式程序员的工具集

?? Functional Programming in Javascript 主目录第二章 函数式编程基础上一节 与函数共舞 函数式程序员的工具集 如果你仔细看了到目前为止出现过的示例代码,你会发现这里面的一些方法不太熟悉. 它们是map().filter()和reduce()函数,它们对任何语言的函数式编程都至关重要. 它们可以让你不必使用循环和语句,写出更简洁的代码. map().filter()和reduce()函数组成了函数式程序员工具集的核心部分,这个工具集包括一系列纯的. 高阶的函

[原创译书] JS函数式编程 前言

前言 函数式编程是一种能够让你编写更聪明的代码的方式,可以减低复杂度,增强模块化. 它是一种通过灵巧地变化.组合.使用函数达到编写简洁代码的方式. Javascript提供了一个实现这些的超赞的途径.Javascript,这个Internet的脚本语言, 它的核心实际上是一个函数式语言.通过学习如何显露出它作为一个函数式语言的真实身份, 我们可以实现强大的.更易维护的以及更可靠的web应用. 通过这些,Javascript的那些怪癖和缺陷将会立刻变得清晰,并且语言本身也将会无限精彩. 学习如何使