资讯专栏INFORMATION COLUMN

尝试在JavaScript中构建一个"Maybe"检测器

bingo / 2760人阅读

摘要:我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。它导致了数不清的错误漏洞和系统崩溃,可能在之后年中造成了十亿美元的损失。这个函数将使用一个表示我们希望进行转换的函数参数,并返回一个包含转换结果的新参数。

翻译原文出处:Building a Maybe in JavaScript
鄙人翻译略差且略有出入,别见笑。

很多时候我们会碰到: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: {}
};

// or

const 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(); // false
maybeNumberTwo.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库: A Maybe monad implementation in JavaScript:

它比Haskell的实现更加灵活,而且还附带了一些额外的功能,考虑到
JavaScript的类型系统限制和语言的一般灵活性。

Point-free 链式函数

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

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

这里可以参考知乎上的JavaScript函数式编程(一)里面的知识点。

所以现在我们可以在我们的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的后续文章。

::完毕::

更多阅读

Elm入门实践(一)——基础篇

函数式编程入门教程

JavaScript函数式编程(一)

JavaScript函数式编程(二)

JavaScript函数式编程(三)

函数式JavaScript(2):如何打造“函数式”编程语言?

A Maybe monad implementation in JavaScript

Functional Programming in Javascript

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85182.html

相关文章

  • React:"don't fuck it up like Google did

    摘要:核心开发人员大神在开了个,用来征询社区对的建议。而且的工程师并没有因此止步,他们在文档中又告诉开发者,不仅仅要把写到中,也应该写到中。无论怎么使用自定义语法,也不应该影响这种好处,即使最终实现看起来有一些怪异。 React 核心开发人员 sebmarkbage 大神在 GitHub 开了个 issues,用来征询社区对 JSX 2.0 的建议。 showImg(https://segm...

    Cristalven 评论0 收藏0
  • Google推出的爬虫新神器:Pyppeteer,神挡杀神,佛挡杀佛!

    摘要:注意,是叫做,不是。两款浏览器同根同源,它们有着同样的,但配色不同,由蓝红绿黄四种颜色组成,而由不同深度的蓝色构成。另外是基于的新特性实现的,所以它的一些执行也支持异步操作,效率相对于来说也提高了。是否响应信号,一般是命令,默认是。 如果大家对 Python 爬虫有所了解的话,想必你应该听说过 Selenium 这个库,这实际上是一个自动化测试工具,现在已经被广泛用于网络爬虫中来应对 ...

    Fundebug 评论0 收藏0
  • 《HelloGitHub》第 68 期

    摘要:整个项目简单还具有实用价值,可作为的实战项目学习的调试工具栏。查看文档自动在个人首页展示编程时长的工具。通过学习这些前沿的人工智能论文,提前了解在未来更多可能性可以将图片和视频转换成漫画风格的工具。兴趣是最好的老师,HelloGitHub 让你对编程感兴趣!简介HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。https://github.com/521xueweihan...

    番茄西红柿 评论0 收藏2637
  • 前端开发环境如何搭建

    1、node环境与开发工具准备前端工程化开发,本地启动开发环境都是基于nodejs的,命令行里输入指令node -v可以帮助确认您的电脑上是否安装了node,如果没有安装,则可以去 node官网 下载安装包,如下图所示: 不管是在windows还是在mac环境下,都是一键傻瓜式安装,甚至连环境变量都不需要手动配置,这里就不过多介绍了。2、开发工具准备前端的开发工具,常见的有VSCode、WebS...

    社区管理员 评论0 收藏0
  • Html/Css/Jquery知识点集锦

    摘要:首先,巧妙的使用这一标记,将游览器从所有情况中分离出来。接着,再次使用将和分离开来,这样已经独立识别。元素不能用作语义用途以外的其他目的。Html1、Html5有哪些新特性,移除了哪些元素?如何处理HTML5新标签的浏览器兼容问题?如何区别HTML和HTML5?HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加。拖拽释放(Drag and drop) ...

    shiweifu 评论0 收藏0

发表评论

0条评论

bingo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<