函子(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 主目录第五章 范畴论

转载于:https://www.cnblogs.com/tolg/p/5258029.html

JS函数式编程【译】5.2 函子 (Functors)相关推荐

  1. JS函数式编程概念理解:函子(Functor)

    标签(空格分隔): 函数式编程 函子 functor 很多前端在学习函数式编程之前,都会被各种概念折磨的死去活来,本文的重点算是函数式编程之前的一个甜品,重点在如何切入. 函子即Functor是FP( ...

  2. 【基于JS 函数式编程 -1】什么是函数式编程 | 纯函数 | 命令式与声明式 | 优点

    ⭐️ 本文首发自 前端修罗场(点击即可加入),一个专注 Web 技术.答疑解惑.面试辅导.职业发展的社区. 相关文章 [函数式编程]基于JS 进行函数式编程(一)引入 | 什么是函数式编程 | 函数式 ...

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

    单子是帮助你组合函数的工具. 像原始类型一样,单子是一种数据结构,它可以被当做装载让函子取东西的容器使用. 函子取出了数据,进行处理,然后放到一个新的单子中并将其返回. 我们将要关注三种单子: May ...

  4. 简单介绍函数式编程中的Functor(函子),Applicative(加强版函子),Monad(单子)

    原文地址:http://skaka.me/blog/2015/12/19/functor-applicative-monad-scala-haskell/ 如果你是刚接触函数式编程,可能很容易被下面这 ...

  5. js 函数式编程(一)

    函数式编程,以强调函数使用为主的开发风格,也是一种范式 函数为一等公民 js函数是一个特殊的对象,有很高的灵活性,比如函数返回一个函数,函数闭包,函数作为函数的参数,函数赋给变量,如果某个编程语言的函 ...

  6. JS函数式编程思维:柯里化、闭包

    偏函数(Partial Application): 探讨柯里化之前,我们先聊一聊很容易跟其混淆的另一个概念--偏函数(Partial Application).在维基百科中,对 Partial App ...

  7. js函数式编程最佳实践 - 持续更新

    函数式编程最佳实践 学习文档 函数式编程术语 数组字串处理 function addString(el){return el + "0"; } var newArr = arr.m ...

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

    ? Functional Programming in Javascript 主目录第五章 范畴论 范畴论 范畴论是用于函数组合的理论性概念.范畴论和函数组合它俩在一起就像发动机排量和马力,像NASA ...

  9. 【读书笔记】《JS函数式编程指南》(一)

    纯函数 纯函数:函数的返回值由传入的参数决定,即相同的参数返回相同的结果. slice和splice,表现作用相似. slice浅复制,返回复制之后的数组 splice删除数组元素,返回删除元素 sl ...

最新文章

  1. 读书笔记:《少的力量》
  2. 如何在Javascript中访问对象的第一个属性?
  3. Linux-鸟菜-6-文件与目录管理
  4. 进大厂全靠自学,微软amp;头条实习生现身说法:我是这样自学深度学习的丨课程传送门...
  5. 超全Typora快速入门
  6. jdk8 Function
  7. java对象的内存结构_Java对象在内存中的结构分析
  8. win10计算器rsh_酷到你认不出!新Win10计算器上手体验
  9. 世界质量大师登场,告诉你质量的最大秘密!【优思学院】
  10. 什么是利亚诺夫指数?Lyapunove指数 李雅普诺夫指数
  11. 基于RetinaFace+ArcFace的人脸识别测试和验证代码
  12. header html 高度,CSS经典基础布局,自适应高度。header,content,footer.
  13. 社保到底是多交好,还是少交好?
  14. 回到那个夏天(千与千寻)
  15. iOS访问 self-signed(自签名) HTTPS
  16. 谷歌小恐龙-有网也能玩儿
  17. mysql list dbs 代替_mysql_list_dbs函数的用法实例汇总
  18. 什么是 TF-IDF 算法?
  19. 如何语音翻译成中文?怎么把语音翻译成文字?
  20. 系统自己弹出诸如 kernel:NMI watchdog: BUG: soft lockup - CPU#2 stuck for 26s [mysqld:2875]

热门文章

  1. 服务器平均响应时长计算,并发数 = QPS*平均响应时间
  2. stk 坐标系_STK学习-坐标系
  3. mongo在哪创建管理员_MongoDB初始化创建管理员账户登录
  4. java解数独_java解数独
  5. pythonclass全局变量_Python-多处理全局变量更新未返回给父级
  6. vue项目nginx部署子目录_vue 多项目部署---二级目录
  7. c java 的关系,c#与c、java的关系
  8. mysql 字段必填 属性_如何判断数据库中的字段是否具有必填属性(50分)
  9. linux共享存储通信实验,Linux进程通信——共享存储
  10. GitHub:TensorFlow、PyTorch最全资料集锦