摘要:函子上面容器上定义了方法,的定义也类似是实现了函数并遵守一些特定规则的容器类型。不同类型的函子容器在处理内部值时,经常遇到传入参数异常的情况的情况,检查值的合理性就非常重要。函子保证在调用传入的函数之前,检查值是否为空。
最近一直在学习函数式编程,前面介绍了函数式编程中非常重要的两个运算函数柯里化 和 函数组合,下文出现的curry 和 compose函数可以从前两篇文章中找到。它们都可以直接在实际开发中用到,写出函数式的程序。
本文主要初探容器的相关概念,以及如何处理编程中异步操作,错误处理等依赖外部环境状态变化的情况,,
容器(container)容器可以想象成一个瓶子,也就是一个对象,里面可以放各种不同类型的值。想想,瓶子还有一个特点,跟外界隔开,只有从瓶口才能拿到里面的东西;类比看看, container 回暴露出接口供外界操作内部的值。
一个典型的容器示例:
var Container = function(x) { this.__value = x; } Container.of = function(x) { return new Container(x); } Container.of("test") // 在chrome下会打印出 // Container {__value: "test"}
我们已经实现了一个容器,并且实现了一个把值放到容器里面的 Container.of方法,简单看,它像是一个利用工厂模式创建特定对象的方法。of方法正是返回一个container。
函子(functor)上面容器上定义了of方法,functor的定义也类似
Functor 是实现了map函数并遵守一些特定规则的容器类型。
把值留在容器中,只能暴露出map接口处理它。函子是非常重要的数据类型,后面会讲到各种不同功能的函子,对应处理各种依赖外部变量状态的问题。
Container.prototype.map = function(f) { return Container.of(f(this.__value)) }
把即将处理容器内变量的函数,包裹在map方法里面,返回的执行结果也会是一个Container。
这样有几点好处:
保证容器内的value一直不会暴露出去,
对value的操作方法最终会交给容器执行,可以决定何时执行。
方便链式调用
// 利用上一篇中讲到的柯里化,就可以看出其特性。 var add2 = function(x, y) { return x + y; }; curriedAdd = curry(add2); Container.of(2).map(curriedAdd(3)); // Container {__value: 5}不同类型的函子 maybe
容器在处理内部值时,经常遇到传入参数异常的情况的情况,检查value 值的合理性就非常重要。Maybe 函子保证在调用传入的函数之前,检查值是否为空。
var Maybe = function(x) { this.__value = x; } Maybe.of = function(x) { return new Maybe(x); } Maybe.prototype.isNothing = function() { return (this.__value === null || this.__value === undefined); } Maybe.prototype.map = function(f) { return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value)); }
这个通用的例子,体现了输出结果的不确定性,也可以看出,在容器内部所有对value值的操作,都会交给容器来执行。在value 为空的情况下,也会返回包裹着null的容器,避免后续容器链式调用报错。
异常捕获函子通常 使用throw/catch就可以捕获异常,抛出错误,但它并不是一种纯函数方法,最好的方法是在出现异常时,可以正常返回信息。Either函子,内部两个子类Left 和 right; 可以看成右值是正常情况下使用并返回的值,左值是操作简单的默认值。
var Left = function(x) { this.__value = x; } Left.of = function(x) { return new Left(x); } Left.prototype.map = function(f) { return this; } var Right = function(x) { this.__value = x; } Right.of = function(x) { return new Right(x); } Right.prototype.map = function(f) { return Right.of(f(this.__value)); } // 输入数据进行校验 var setage = function(age) { return typeof age === "number"? Right.of(age): Left.of("error age") } setage(12).map(function(age){return "my age is" + age}) // Right {__value: "my age is12"} setage("age").map(function(age){return "my age is" + age}) // Left {__value: "error age"}
left 和 right 唯一的区别在于map 方法的实现,当然,一个函子最大的特点也体现在map方法上,
Left.map 不管传入的是什么函数,直接返回当前容器;Right.map则是示例里面的方法一样。
IO 操作本身就是不纯的操作,生来就得跟外界环境变量打交道,不过可以掩盖他的不确定性。跟下面localStorage包裹函数类似,延迟执行IO 操作。
var getStorage = function(key) { return function() { return localStorage[key]; } }
再看看,封装了高级一点的IO 函子:
var IO = function(f) { this.__value = f; } IO.of = function(x) { return new IO(function(){ return x; }) } IO.prototype.map = function(f) { // 使用上一句定义的compose函数 return new IO(compose(f, this.__value)) }
compose函数组合,里面存放的都是函数,this.__value跟其他函子内部值不同,它是函数。IO.of方法在new对象之前,把值包裹在函数里面,试图延迟执行。
// 测试一下 var io__dom= new IO(function() {return window.document}) io__dom.map(function(doc) { return doc.title}) // IO {__value: ƒ}
返回一个没有执行的函数对象,里面的__value值对应的函数,在上面函数调用后并没有执行,只有在调用了this.__value值后,才执行。最后一步不纯的操作,交给了函数调用者去做。
Monad一个functor, 只要他定义了一个join 方法和一个of 方法,那么它就是一个monad。 它可以将多层相同类型的嵌套扁平化,像剥洋葱一样。关键在于它比一般functor 多了一个join 方法。 我们先看看剥开一层的join方法。
var IO = function(f) { this.__value = f } IO.of = function(x) { return new IO(function(){ return x; }) } IO.prototype.join = function() { return this.__value ? this.__value(): IO.of(null); } // 包裹上两层 var foo = IO.of(IO.of("test bar")); foo.join().__value(); // 返回里面嵌套着的IO类。 IO {__value: ƒ},接着只需调用这里的__value(),就可以返回字符串`test bar`;
回头看看前面map方法,return new IO(),生成新的容器,方便链式调用,跟 join方法结合一起使用,生成容器后,再扁平化。形成 chain 函数,
var chain = curry(function(f, m) { return m.map(f).join(); })
看一个完整示例,其中curry 和compose,分别用到了链接里面的实现,:
var IO = function(f) { this.__value = f; } IO.of = function(x) { return new IO(function() { return x; }) } IO.prototype.map = function(f) { // 使用上一句定义的compose函数 return new IO(compose(f, this.__value)) } IO.prototype.join = function() { return this.__value ? this.__value() : IO.of(null); } var chain = curry(function(f, m) { return m.map(f).join(); }) var log = function(x) { return new IO(function() { console.log(x); return x; }) } var setStyle = curry(function(sel, props) { return new IO(function() { return document.querySelector(sel).style.background = props }) }) var getItem = function(key) { return new IO(function() { return localStorage.getItem(key); }) }; var map = curry(function(f, functor) { return functor.map(f); }); // 简单实现join var join = function(functor) { return functor.join(); } localStorage.background = "#000"; var setItemStyle = compose(join, map(setStyle("body")), join, map(log), getItem); // 换成 链式调用。 setItemStyle = compose(chain(setStyle("body")), chain(log), getItem); setItemStyle("background").__value(); // 操作dom 改变背景颜色总结
本文主要利用简单代码举例,介绍了容器,函子等相关概念,初步认识了各种不同的函子。深入实践示例,可以参考阅读下面链接:
函数式编程风格
js函数式编程指南https://llh911001.gitbooks.io...
JavaScript函数式编程(二)
JavaScript:函数式编程基本概念学习
JS函数式编程 - 函子和范畴论
javascript函数式编程之 函子(functor)
函数式编程入门教程
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/102475.html
摘要:函数式编程二拖延症了好久,第二篇终于写出来了。如果你对熟悉的话应该还记得,是可以调用来集中处理错误的对于函数式编程我们也可以做同样的操作,如果运行正确,那么就返回正确的结果如果错误,就返回一个用于描述错误的结果。 JavaScript函数式编程(二) 拖延症了好久,第二篇终于写出来了。 上一篇在这里:JavaScript函数式编程(一) 上一篇文章里我们提到了纯函数的概念,所谓的纯函数...
摘要:入门的导语废话最近两年你要说函数式编程不火的话那是不可能的是人都知道函数式编程很火为什么函数式编程会火呢在于它的思想很强大很强势尤其是前端的更是在上完全使用纯函数函数式的好处渐渐被发掘出来笔者最近看了一些函数式方面的东东现在发出来给大家学习 0x00 入门的导语(废话) 最近两年你要说函数式编程不火的话, 那是不可能的, 是人都知道函数式编程很火.为什么函数式编程会火呢, 在于它的思想...
摘要:函数式编程函数式,可能并不是那么难。在学习函数式编程之初,首先要知道在这一技能叶子中包含有多少个相关词,其次要知道它和我们是否从未有过遇见。 JS函数式编程 函数式,可能并不是那么难。 在学习JS函数式编程之初,首先要知道在这一技能叶子中包含有多少个相关词,其次要知道它和我们是否从未有过遇见。 一等公民、纯函数、柯里化、代码组合、pointfree、命令式与申明式、 Hindley...
阅读 1611·2019-08-30 12:51
阅读 637·2019-08-29 17:30
阅读 3646·2019-08-29 15:17
阅读 831·2019-08-28 18:10
阅读 1291·2019-08-26 17:08
阅读 2146·2019-08-26 12:16
阅读 3398·2019-08-26 11:47
阅读 3461·2019-08-23 16:18