你的函数是什么颜色?(2015)
摘要
一篇2015年的博文,提出一个假设的编程语言,其中函数被标记为红色或蓝色,借此比喻批评JavaScript等语言中同步与异步函数的人为区分。
暂无内容
查看缓存全文
缓存时间: 2026/05/26 21:57
# 你的函数是什么颜色? – journal.stuffwithstuff.com 来源:https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ 我不知道你是怎么想的,但对我来说,没有什么能比一场酣畅淋漓的语言吐槽更让我早上精神抖擞的了。看到有人痛斥那些凡夫俗子们使用的“blub”(http://www.paulgraham.com/avg.html)语言,简直让人热血沸腾。这些凡人每天靠着偷偷摸摸访问 StackOverflow 来应付日子。(而你呢,和我一样,只用最开明的语言。那些为专家工匠们精心打造的工具,就像我们一样手艺精湛。) 当然,作为这篇檄文的*作者*,我冒了风险。我嘲笑的可能是你喜欢的一种语言!我没意识到,我可能已经把暴民带进了我的博客,他们手持干草叉和火把,我这篇鲁莽的小册子可能会引来他们的愤怒!为了避免引火烧身,也为了不冒犯你也许脆弱的审美,我将改为吐槽一种我瞎编的语言。一个纯粹用来点燃的稻草人。我知道,这看上去毫无意义对吧?相信我,到最后,我们会看到那稻草人的脑袋上画着谁的脸(或者,是哪些脸!)。 ## 一种新语言\#a\-new\-language (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#a-new-language) 为了写一篇博客文章就学习一整套新的(糟糕的)语言有点强人所难,所以我们假设它和你我已知的语言差不多。我们假定它的语法有点像 JavaScript,带花括号和分号,有 `if`、`while` 等。是程序猿圈的“通用语”。我选 JS *并不是*因为这篇文章主要讲它,而是因为你——统计意义上的典型读者——最有可能理解它。瞧: `` function thisIsAFunction() { return "It's awesome"; } `` 因为我们的稻草人是一种*现代*(糟糕的)语言,它也有一等函数。所以你可以写出这样的东西: `` // Return a list containing all of the elements in collection // that match predicate. function filter(collection, predicate) { var result = []; for (var i = 0; i < collection.length; i++) { if (predicate(collection[i])) result.push(collection[i]); } return result; } `` 这是一种*高阶*函数,顾名思义,它们非常优雅且超级有用。你可能已经习惯用它们来操作集合,但一旦你内化了这个概念,你就会发现几乎到处都用得上它们。也许在你的测试框架里: `` describe("An apple", function() { it("ain't no orange", function() { expect("Apple").not.toBe("Orange"); }); }); `` 或者当你需要解析一些数据时: `` tokens.match(Token.LEFT_BRACKET, function(token) { // Parse a list literal... tokens.consume(Token.RIGHT_BRACKET); }); `` 所以你火力全开,写出各种各样牛逼的可复用库和应用程序,到处传递函数、调用函数、返回函数。函数狂欢节。 ## 你的函数是什么颜色?\#what\-color\-is\-your\-function (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#what-color-is-your-function) 等等。这里我们的语言开始变得奇怪了。它有一个独特特性: **1. 每个函数都有颜色。** 每个函数——无论是匿名回调还是常规命名函数——要么是红色,要么是蓝色。不是用一个 `function` 关键字,而是有两个: `` blue_function doSomethingAzure() { // 这是一个蓝色函数…… } red_function doSomethingCarnelian() { // 这是一个红色函数…… } `` 这门语言中*没有*无颜色的函数。想创建函数?必须选一种颜色。这是规则。而且,实际上还有几条规则也要遵守: **2. 调用函数的方式取决于它的颜色。** 想象一下“蓝色调用”语法和“红色调用”语法。类似: `` doSomethingAzure()blue; doSomethingCarnelian()red; `` 调用函数时,你需要使用与它颜色对应的调用方式。如果你弄错了——用 `blue` 调用红色函数,反之亦然——就会发生一些糟糕的事。唤起你童年时某个被遗忘的噩梦,比如一个胳膊是蛇的小丑藏在你床底下,然后从你的显示器里跳出来,吸干你的玻璃体。很烦人的规则对吧?哦,还有一条: **3. 你只能在另一个红色函数内部调用红色函数。** 你*可以*在红色函数内部调用蓝色函数。这是允许的: `` red_function doSomethingCarnelian() { doSomethingAzure()blue; } `` 但反过来不行。如果你尝试这样做: `` blue_function doSomethingAzure() { doSomethingCarnelian()red; } `` 那么,你就要拜访一下老蜘蛛嘴——夜行小丑了。 这使得编写像我们例子中 `filter()` 这样的高阶函数变得更棘手。我们必须为*它*选一种颜色,而这又会影响我们可以传递给它的函数的颜色。显而易见的解决方案是把 `filter()` 变成红色。这样,它可以接受红色或蓝色函数并调用它们。但紧接着,我们就遇到了这件语言“麻衣”上的另一个瘙痒处: **4. 红色函数调用起来更痛苦。** 现在,我不会精确地定义“痛苦”,但请设想一下,程序员每次调用一个红色函数都得跳过一些烦人的障碍。也许是很啰嗦,或者不能在特定类型的语句中调用,也许只能在质数的行号上调用。重要的是,如果你决定让一个函数变红,那么每个使用你 API 的人都会想往你的咖啡里吐口水,或者更不敬的液体。 显而易见的解决方案当然是*永远不要*使用红色函数。把所有东西都变成蓝色,你就回到了理智的世界,所有函数颜色相同,这等价于它们都没有颜色,等价于我们的语言不那么愚蠢。 然而,这些虐待狂的语言设计师——我们都知道所有编程语言设计师都是虐待狂,不是吗?——又在我们身上插了最后一根刺: **5. 某些核心库函数是红色的。** 平台内置了一些函数,我们*必须*使用的函数,我们无法自己编写,它们只有红色版本。在这一点上,一个理性的人可能会觉得这门语言恨我们。 ## 这是函数式编程的错!\#its\-functional\-programmings\-fault (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#its-functional-programmings-fault) 你可能在想,问题在于我们试图使用高阶函数。如果我们不再到处炫耀那些函数式花哨,而是像上帝本意那样编写朴素的蓝领一阶函数,就能省去所有烦恼。如果我们只调用蓝色函数,就让我们的函数变蓝。否则就变红。只要我们不创建接受函数的函数,就不必担心什么“函数颜色多态”(“多色性”?)之类的废话。 但可惜,高阶函数只是一个例子。任何时候我们想把程序分解成可复用的不同函数时,这个问题都会出现。例如,假设我们有一段不错的代码块,它实现了在某张社交网络迷恋关系图上的狄克斯特拉算法。(我花了太长时间试图想明白这个结果到底代表什么。传递性的不被渴望?)后来,你需要在别处使用同样的代码块。你做了自然而然的事:把它提取成一个单独的函数。在旧地方和新的使用它的代码中调用它。但应该是什么颜色?显然,如果可能你会把它弄成蓝色,但如果它使用了某个讨厌的红色专属核心库函数呢?如果调用它的新地方是蓝色呢?你就得把它变红。然后你又得把调用*它的*那个函数变红。唉。不管怎样,你都得不停地思考颜色。它会像沙滩度假时泳裤里的沙子。 ## 多彩的寓言\#a\-colorful\-allegory (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#a-colorful-allegory) 当然,我并不是真的在谈论颜色,对吧?这是一个寓言,一种文学手法。《史尼奇》说的不是肚子上的星星,而是种族。现在,你大概已经猜到了颜色实际上代表什么。如果没有,那么这里是大揭秘: **红色函数就是异步函数。** 如果你在用 Node.js 写 JavaScript,每次定义一个通过调用回调来“返回”值的函数,你就创建了一个红色函数。回头看看那些规则列表,看看我的比喻是如何对应的: 1. 同步函数返回值,异步函数不直接返回值,而是调用回调。 2. 同步函数通过返回值给出结果,异步函数通过调用你传递给它的回调来给出结果。 3. 你不能从同步函数中调用异步函数,因为你无法在异步函数完成之前确定结果。 4. 异步函数由于回调而不能在表达式中组合,有不一样的错误处理方式,不能用于 `try/catch` 或许多其他控制流语句中。 5. Node 的整个卖点就是核心库都是异步的。(不过它们后来改了点,给很多函数加上了 `___Sync()` 版本。) 当人们谈论“回调地狱”时,他们就是在说语言中有红色函数有多烦人。当他们创建了 4,089 个用于异步编程的库(https://www.npmjs.com/search?q=async)时,他们就是在库层级上应对这样一个语言强加给他们的难题。 *更新于 2021/12/03:* 今天有 15,118 个异步库。 ## 我承诺未来更美好\#i\-promise\-the\-future\-is\-better (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#i-promise-the-future-is-better) Node 社区的人们早就意识到回调很烦人,并一直在寻找解决方案。一种让许多人兴奋的技术是*Promise*(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise),你可能也知道它的说唱名字“future”。这本质上是对回调和错误处理器的某种加强版包装。如果你把向一个函数传递回调和错误回调看作一个*概念*,那么 promise 基本就是那个概念的*具体化*。它是一个代表异步操作的一等对象。上面这句话里我塞了一堆高级 PL 行话,所以听起来好像很厉害,但这基本上就是蛇油。Promise*确实*让异步代码写起来稍微容易一些。它们组合性稍微好点,所以规则 #4 不再*那么*烦人。但说实话,这就像被一拳打在肚子上和被打在私处之间的区别。技术上更不痛,是的,但我不认为谁应该真的对这个价值主张感到兴奋。你仍然不能将它们用于异常处理或其他控制流语句。你仍然不能从同步代码中调用返回 future 的函数。(嗯,你*可以*,但如果你这么做了,将来维护你代码的人会发明一台时间机器,回到你干这件事的时刻,然后用一支 2 号铅笔捅你的脸。)你仍然把你的整个世界分成了异步和同步两半,以及随之而来的所有痛苦。所以,就算你的语言有 promise 或 future,它的脸看起来也跟我的稻草人非常像。(是的,这甚至包括 Dart(http://dartlang.org/),我所在的语言。这就是为什么我很兴奋团队里有些人在实验其他并发模型(https://github.com/dart-lang/fletch)。) ## 我在 await 一个解决方案\#im\-awaiting\-a\-solution (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#im-awaiting-a-solution) C# 的程序员们现在可能感觉很自鸣得意(随着 Hejlsberg 和他的团队在语言中堆砌了一个又一个甜美的特性,他们越来越容易陷入这种状态)。在 C# 中,你可以使用 `await` 关键字(https://msdn.microsoft.com/en-us/library/hh191443.aspx)来调用异步函数。这使得你调用异步函数就像调用同步函数一样容易,只多加一个可爱的小关键字。你可以在表达式中嵌套 `await` 调用,在异常处理代码中使用它们,把它们塞进控制流里。尽情使用吧。让 `await` 调用像你新说唱专辑的预付款钞票一样飞舞。 Async-await*确实*不错,这也是为什么我们正在把它加到 Dart 里。它让编写异步代码容易得多。你知道“但是”要来了。嗯,对。*但是……*你仍然把世界分成了两半。那些异步函数写起来更容易了,但*它们仍然是异步函数*。你仍然有两种颜色。Async-await 解决了烦人的规则 #4:它让调用红色函数不比蓝色函数差太多。但其他所有规则仍然存在: 1. 同步函数返回值,异步函数返回包装值的 `Task`(或 Dart 中的 `Future`)包装器。 2. 同步函数直接调用,异步函数需要 `await`。 3. 如果你调用一个异步函数,你得到的是这个包装对象,而你实际上想要的是 `T`。你无法拆包装,除非你把*你的*函数变成异步并 await它。(但看下面。) 4. 除了大量撒上 `await` 之外,我们至少解决了这个问题。 5. C# 的核心库实际上比 async 更老,所以我想他们从来没有这个问题。 它*确实*更好。我宁愿用 async-await 也不必用裸回调或 future。但如果我们认为所有麻烦都消失了,就是在自欺欺人。一旦你开始尝试编写高阶函数,或者复用代码,你就会立刻意识到颜色仍然存在,在你的代码库中到处流血。 ## 什么语言*没有*颜色?\#what\-language\-isnt\-colored (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#what-language-isnt-colored) 所以 JS、Dart、C# 和 Python 都有这个问题。CoffeeScript 和大多数编译到 JS 的语言也都有(这是为什么 Dart 继承了它)。我*觉得*连 ClojureScript 也有这个问题,尽管他们很努力地通过 core.async(https://github.com/clojure/core.async)来对抗它。想知道哪种语言没有吗?*Java。*对吧?你多久才能说一次,“没错,Java 才是真正做对了这个的”?但确实。不过在他们(C#/etc)的辩护中,他们正在积极地通过转向 future 和异步 IO 来纠正这个疏忽。这就像一场向下竞争。C# 实际上本来*可以*避免这个问题的。他们*选择了*拥有颜色。在他们加入 async-await 和所有 `Task` 相关的东西之前,你就直接使用常规的同步 API 调用。还有三种语言没有这个问题:Go、Lua 和 Ruby。猜到他们有什么共同点了吗?*线程。*或者更精确地说:*多个可以切换的独立调用栈*(https://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/)。它们不一定是操作系统线程。Go 中的 goroutine、Lua 中的协程、Ruby 中的 fiber 就完全足够。(这就是为什么 C# 有那个小例外:你可以通过使用线程来避免 C# 中异步的痛苦。) ## 往事追忆\#remembrance\-of\-operations\-past (https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/#remembrance-of-operations-past) 根本问题是:“当一个操作完成时,你如何从你离开的地方继续执行?”
相似文章
你的十六进制编辑器应该给字节上色
一篇博客文章主张,十六进制编辑器应为字节着色,以便让二进制数据中的模式更易被察觉和分析。
算法主题引擎
本文介绍了新的CSS `contrast-color()`函数,该函数允许开发人员自动选择黑色或白色文本,以实现与任何背景颜色的可访问对比度,解决网络上长期存在的低对比度问题,而无需依赖JavaScript。
代数效应:给普通人的解释
这是一篇教育性博客文章,通过类比 try/catch 和 async/await 来解释编程中的代数效应概念,并讨论了它们与 React 及未来编程范式的潜在关联。
7行代码,3分钟:实现一种编程语言(2010)
本文介绍了一种基于 Lambda 演算的图灵完备函数式语言的极简 7 行解释器,展示了 eval/apply 设计模式。
一种为人类设计的编程语言
Eat Your Greens(EYG)是一种静态类型函数式编程语言,旨在通过消除系统级关注点来简化终端用户编程,让创客能够专注于问题逻辑。