博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
尝试在JavaScript中构建一个"Maybe"检测器
阅读量:6037 次
发布时间:2019-06-20

本文共 3927 字,大约阅读时间需要 13 分钟。

翻译原文出处:

鄙人翻译略差且略有出入,别见笑。

很多时候我们会碰到:Uncaught TypeError: Cannot read property 'x' of undefined(无法读取未定义的属性“x”)。

我猜,如果你正好看到这个你以前不单只碰过还历历在目的东西,可能有那么一刻想把显示器给砸了。
这里想起了我们尊敬的计算机领域的爵士——托尼·霍尔;他在Infoq办的大会演讲时,用到的主题是:“Null References: The Billion Dollar Mistake”(Null 引用:一个十亿美元级别的错误),讲演摘要中这样写的:
“我把Null引用称为自己的十亿美元错误。它的发明是在1965年,那时我用一个面向对象语言( ALGOL W )设计了第一个全面的引用类型系统。我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了Null引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后40年中造成了十亿美元的损失。近年来,大家开始使用各种程序分析程序,比如微软的PREfix和PREfast来检查引用,如果存在为非Null的风险时就提出警告。更新的程序设计语言比如Spec#已经引入了非Null引用的声明。这正是我在1965年拒绝的解决方案。”

那十亿美元级别的错误

幸运的是,我们可以使用一些功能性的编程技术,以清洁、简洁和可靠的方式缓解这带来疼痛。让我们想象一下,我们要从下面的对象中提取属性“c”的值,并附加字符串“is great”。

const a = {    b: {        c: "fp"    }};

我们使用的简单方法可能是:

const appendString = (obj) =>    obj.b.c + " is great";    appendString(a);

这样的写法很棒,但可悲的是a对象并非是一成不变的。因此,我们收回的数据有时会采取到以下的形式:

const a = {    b: {}};// orconst a = {};

当这个时候我们调用了appendString函数时,整个宇宙将会爆炸的...

无法读取未定义的属性“c”

这个时候可能要我们对函数的传参进行空检查:

const appendString = (obj) => {    if (!obj || !obj.b || !obj.b.c || !) return null;    return obj.b.c + " is great";}

这是有效的,但它看起来很丑陋和很容易出错。我们必须对传参进行每种类型的对象定义特定的(正确的)空检查,这是不是很有趣(复杂)。

哈哈哈,这个时候可能Maybe就派得上场了。

Maybe的基本用法

基本上,我们都会将要构建的对象封装其值可能为null的概念,并且考虑到随之而来的复杂性。在学习Elm(一门专注于Web前端的纯函数式语言)之后,我会在Maybe上的封装了两个概念状态Maybe.justMaybe.nothing。对于初学者,我们简单地定义一个返回一个布尔值的isNothing方法,告诉我们Maybe是否不包含任何内容:

const isNullOrUndef = (value) => value === null || typeof value === "undefined";const maybe = (value) => ({    isNothing: () => isNullOrUndef(value)});

甚至使用一个简单的工厂函数来创建我们的Maybe - 考虑到往后可能会添加更多的方法,我们将使用一个对象来定义它:

const Maybe = {    just: maybe,    nothing: () => maybe(null)};

所以现在我们可以这样做:

const maybeNumberOne = Maybe.just("a value");const maybeNumberTwo = Maybe.nothing();maybeNumberOne.isNothing(); // falsemaybeNumberTwo.isNothing(); // true

一切都很好,但到目前为止还不是很实用。编程是关于转换数据的,所以我们需要一种改变我们的Maybe的方法 - 一个map函数。这个map函数将使用一个表示我们希望进行转换的函数参数,并返回一个包含转换结果的新参数。重要的是,如果maybe不包含任何内容,那么该函数将不会被应用,我们将返回一个新的maybe.nothing方法。

const maybe = (value) => ({    isNothing: () => isNullOrUndef(value),    map: (transformer) => !isNullOrUndef(value) ? Maybe.just(transformer(value)) : Maybe.nothing()});

现在我们可以这样来调用maybe实现:

const maybeOne = Maybe.just(5);maybeOne.map(x => x + 1); // Maybe.just(6);const maybeTwo = Maybe.nothing();maybeTwo.map(x => x + 1) // Maybe.nothing();

关键一点的是maybe.map返回一个新的maybe,所以我们可以将这些操作链接在一起。回到我们现在可以做的最初的问题:

const a = {    b: {        c: "fp"    }};const maybeA = Maybe.just(a)    .map(a => a.b)    .map(b => b.c)    .map(c => c + " is great!");

这里的好处是,如果链中的任何步骤返回null,我们仍然会得到结果Maybe.nothing的结果,而不是运行时错误。

好了,在Github上面有个maybe.js库: :

它比Haskell的实现更加灵活,而且还附带了一些额外的功能,考虑到

JavaScript的类型系统限制和语言的一般灵活性。

Point-free 链式函数

如果你看过我以前发布的文章,那么你就会想我们可以弄得比这更好。我们可以创建从对象中提取命名属性的高阶函数,以及用于追加字符串:

const prop = (propName) => (obj) => obj[propName];const append = (appendee) => (appendix) = appendee + appendix;

这里可以参考知乎上的里面的知识点。

所以现在我们可以在我们的map链式中使用这个功能:

const a = {    b: {        c: "fp"    }};const maybeA = Maybe.just(a)    .map(prop("b"))    .map(prop("c"))    .map(append(" is great!"));

好了,我们现在终于到了这一步, 我们已经处理了空检查,并将其重构为point-free形式。接下来需要做的就是使逻辑可重用性;我们想要的是能够将我们的功能传递给一个功能,并应用所有步骤,以便我们可以在许多不同的Maybe上重新使用提取器:

const extractor = // what we're about to make    extractor(Maybe.just(a)); // Maybe.just("fp is great")

我们需要的是一个需要我们的步骤的函数,并且每个步骤依次调用我们Maybe.map方法。我们将调用函数Maybe.chain,我们可以用reducer来实现:

const Maybe = {    just: maybe,    nothing: () => maybe(null),    chain: (...fns) => (input) => fns.reduce((output, curr) => output.map(curr), input)};

我们现在可以构建一个可以应用于maybe的可用功能:

const appendToC = Maybe.chain(    prop("b"),    prop("c"),    append(" is great!"));

并将其用于各种输入:

const goodInput = Maybe.just({    b: {        c: "fp"    }});const badInput = Maybe.just({});appendToC(goodInput); // Maybe.just("fp is great!")appendToC(badInput); // Maybe.nothing()

虽然我在学习Elm之前不习惯Maybe的价值观概念,但是他们在JavaScript中不想落后啊。如果我们简简单单地去使用它,就只会使用到基础方法而已,所以我们要在它的基础上添加更多的功能函数。所以我将在稍后阅读关于使用Maybe的后续文章。

::完毕::

更多阅读

转载地址:http://svlhx.baihongyu.com/

你可能感兴趣的文章
接口测试(三)--HTTP协议简介
查看>>
周志华《机器学习》课后答案——第4章.决策树
查看>>
frameset分帧问题
查看>>
特殊样式:ime-mode禁汉字,tabindex焦点
查看>>
linux
查看>>
Layout父元素点击不到的解决办法
查看>>
【面试次体验】堆糖前端开发实习生
查看>>
基于apache实现负载均衡调度请求至后端tomcat服务器集群的实现
查看>>
C#+QQEmail自动发送邮件
查看>>
[Hadoop]MapReduce多输出
查看>>
Android Activity详解(一)
查看>>
快准车服完成3000万元A+轮融资,年底将开始B轮融资
查看>>
让我去健身的不是漂亮小姐姐,居然是贝叶斯统计!
查看>>
MySQL 数据约束
查看>>
我的友情链接
查看>>
SERVLET容器简介与JSP的关系
查看>>
《服务器SSH Public Key认证指南》-补充
查看>>
我的友情链接
查看>>
Java break continue return 的区别
查看>>
算法(Algorithms)第4版 练习 1.3.4
查看>>