您的功能是什么颜色?

我不了解您,但是对我来说,今天没有比编程烦恼更好的开始了。 在成功批评平民使用的一种“粗体”语言后,鲜血沸腾了。在整个工作日中,在羞于访问StackOverflow的整个工作日中,这种语言都饱受折磨。


(与此同时,您和我仅使用最开明的语言和为像我们这样的大师们灵巧的手设计的复杂工具)。


当然,作为布道的作者,我要冒险。 您可能喜欢我取笑的语言! 鲁ck的小册子可能不经意间就把一只备有干草叉和火把的愤怒的手机暴民带到了我的博客。


为了保护自己免受正义之火的侵扰,并且不冒犯您的(可能是微妙的)感情,我将讨论该语言...


...谁刚来。 关于稻草肖像,它的唯一作用是将批评家烧死在危急关头。


我知道这听起来很愚蠢,但是请相信我,最终我们将看到谁的脸被画在一根稻草头上。


新语言


只为博客文章学习一种全新的语言是一种矫kill过正的做法,所以可以说它与我们已经知道的语言非常相似。 例如Javascript。 花括号和分号。 if ,等等。 -我们人群中的林瓜弗朗卡


我选择JS并不是因为这篇文章是关于他的。 这只是普通读者可能会使用的一种语言。 瞧:


 function thisIsAFunction(){ return "!"; } 

由于填充动物是一种很酷的 (不好读的)语言,因此它具有一流的功能 。 所以你可以这样写:


 //  ,     , //    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("", function(){ it(" ", function(){ expect("").not.toBe(""); }); }; 

或者,当您需要解析(解析)数据时:


 tokens.match(Token.LEFT_BRACKET, function(token){ // Parse a list literal... tokens.consume(Token.RIGHT_BRACKET); }); 

然后,在加速之后,您将编写各种围绕功能,函数调用,功能从功能返回的功能酷炫的可重用库和应用程序-功能展台。


译者:原文为“ Functapalooza”。 前缀-a-palooza非常酷,您想与所有人共享。

您的功能是什么颜色?


在这里,奇怪的事情开始了。 我们的语言具有一个独特的功能:


1.每个功能都有一种颜色。


每个函数(一个匿名回调或一个带名称的常规函数​​)都是红色或蓝色。 由于在我们的博客中突出显示代码不会突出显示函数的不同颜色,因此我们同意以下语法:


 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.红色功能引起疼痛


我们不会精确指出这种“痛苦”,只是想像一下程序员每次调用red函数时都必须跳过箍。 该调用可能太重音节,或者您无法在某些表达式中运行该函数。 或者,您只能从奇数行访问红色功能。


无关紧要,但是如果您决定将该功能设为红色,则使用您的API的每个人都会想喝点咖啡或做点更糟的事情。


在这种情况下,显而易见的解决方案是永远不要使用红色函数。 只要将所有内容都设为蓝色,您便回到了正常的世界,所有功能都具有相同的颜色,这等于它们没有颜色并且我们的语言并不完全愚蠢。


las,开发了这种语言的施虐者(每个人都知道编程语言的作者是施虐者,对吧?)刺入我们的最后一根刺:


5.该语言的某些核心功能是红色的。


平台中内置的某些功能(我们需要使用的无法自行编写的功能)仅以红色显示。 在这一点上,一个聪明的人可能开始怀疑这种语言讨厌我们。


这都是功能语言的错!


您可能会认为问题在于我们正在尝试使用高阶函数。 如果我们只是按照上帝的计划去闲逛所有这些功能废话,并开始编写正常的蓝色一阶功能(不与其他功能一起使用的功能-大约翻译器),那么我们将摆脱所有这些痛苦。


如果仅调用蓝色函数,则使所有函数变为蓝色。 否则,我们变成红色。 在我们创建接受函数的函数之前,我们无需担心“函数颜色的多态性”(多色?)或其他废话。


但是可惜,高阶函数只是一个例子。 每当我们想将程序分解为可重用的功能时,都会出现问题。


例如,我们有一小段代码,我不知道是在一张图表上实现了Dijkstra的算法,该图表代表了您的社交关系给彼此带来了多少压力。 (我花了很多时间试图确定结果是什么意思。


稍后,您需要在其他地方使用此算法。 自然地,您将代码包装在单独的函数中。 从旧地方和新地方打电话给她。 但是功能应该是什么颜色? 您可能会尝试将其设置为蓝色,但是如果它使用内核库中这些讨厌的“仅红色”功能之一,该怎么办?


假设您要从中调用函数的新位置是蓝色? 但是现在您需要用红色重写调用代码。 然后重做调用此代码的函数。 ew 无论如何,您将不得不不断记住颜色。 这将是您在海滩度假节目中游泳裤中的沙子。


颜色寓言


实际上,我不是在谈论颜色。 这是一个寓言,一种文学手段。 -这不是关于傻瓜上星星 ,这是关于种族。 您可能已经怀疑...


红色功能-异步


如果您使用JavaScript或Node.js进行编程,则每次定义一个调用回调函数 (回调)以“返回”结果的函数时,您都会编写一个红色函数。 查看以下规则列表,并注意它们如何适合我的隐喻:


  1. 同步函数返回结果,异步函数不返回结果;相反,它们调用回调。
  2. 同步函数将结果作为返回值返回,异步函数将其返回,从而导致传递给它们的回调。
  3. 您无法从同步函数中调用异步函数,因为在稍后执行异步函数之前,您无法知道结果。
  4. 异步函数由于回调而不会编译到表达式中,要求对它们的错误进行不同的处理,并且不能在try/catch块或控制程序的许多其他表达式中使用。
  5. 关于Node.js的整个事情是内核库都是异步的。 (尽管他们_Sync()并开始将_Sync()版本添加到很多东西中。)

当人们谈论“回调地狱”时 ,他们谈论的是在其语言中具有“红色”功能是多么令人讨厌。 当他们创建4089个用于异步编程的库时 (在2019年已经是11217-大约翻译器),他们试图在库级别上解决他们被该语言所困扰的问题。


我保证未来会更好


翻译中:“我保证未来会更好”,该部分标题和内容中的单词播放丢失了

Node.js的人们很早就意识到回调会造成伤害,并一直在寻找解决方案。 激励许多人的一种技术是promises ,昵称futures也可能使您知道。


在俄语IT中,不是将“ promises”翻译为“ promises”,而是建立了英语的描图纸-“ promises”。 照原样使用“期货”一词,可能是因为“期货”已经被金融语占据了。

Promis是回调和错误处理程序的包装。 如果您想传递结果的回调和错误的另一个回调,那么future就是这种想法的体现。 这是一个异步操作的基本对象。


我只是听上去很奇怪,这听起来可能是一个很好的解决方案,但主要是蛇油 。 承诺确实使编写异步代码更加容易。 它们更易于组合成表达式,因此规则4的严格程度有所降低。


但老实说,这就像是肚子痛或腹股沟之间的区别。 是的,并没有那么大的伤害,但是没有人会为这样的选择感到高兴。


您仍然无法将Promise与异常处理或其他方法一起使用
管理运营商。 您不能调用从同步代码返回future的函数。 (您可以 ,但是代码的下一个维护者将发明一台时光机,在您执行该操作的那一刻返回,并出于原因2将铅笔粘在脸上。)


承诺仍然将您的世界分为异步和同步两半,随之而来的是所有痛苦。 因此,即使您的语言支持promisesfutures ,它仍然看起来很像我的那种。


(是的,这甚至包括我使用的Dart 。因此,我很高兴团队的一部分正在尝试其他并行方法


项目链接正式放弃

我正在等待解决方案


C#程序员可能会感到沾沾自喜(之所以成为越来越多的受害者,是因为Halesberg和公司将所有内容都撒满了,并在语言中撒上了语法糖)。 在C#中,可以使用await关键字来调用异步函数。


通过添加可爱的little关键字,可以使异步调用和同步调用一样容易。 您可以在指令流中将await调用插入表达式中,并将其用于异常处理中。 你会发疯的。 让您的新说唱歌手专辑像块钱一样下雨。


Async-await很不错,因此我们将其添加到Dart中。 用它编写异步代码要容易得多。 但是,一如既往,只有一个“但是”。 来了 但是...您仍然将世界一分为二。 异步函数现在更易于编写,但是它们仍然是异步函数。


您仍然有两种颜色。 异步等待解决了烦人的问题#4-它们使调用红色函数比调用蓝色函数难得多。 但是其余规则仍然在这里:


  1. 同步函数返回值,异步函数返回围绕该值的包装器(C#中的Task<T>或Dart中的Future<T> )。
  2. 刚刚调用了同步,异步需要await
  3. 通过调用异步函数,可以在确实需要一个值时获得包装对象。 在使函数异步并使用await调用它之前,不能扩展该值(但请参见下一段)。
  4. 除了一点等待装饰,至少我们解决了这个问题。
  5. C#核心库比异步的早,因此我认为他们从来没有这个问题。

Async 确实更好。 我希望在一周的任何一天使用async-await进行裸回调。 但是,如果我们认为所有问题都得到解决,我们就会自欺欺人。 一旦开始编写高阶函数或重用代码,您就会再次意识到颜色仍然存在,并渗入了所有源代码。


哪种语言不是颜色?


因此,JS,Dart,C#和Python都有此问题。 CoffeeScript和大多数其他语言也都使用JS编译(并继承了Dart)。 我认为,尽管ClojureScript一直在与core.async共同努力,但仍具有这一优势。


想知道哪一个不是吗? 爪哇 我说的对吗 您多久说一次:“是的,仅Java就是在做对事情”? 事情就这样发生了。 在辩护中,他们正在积极尝试通过促进futures和异步IO来纠正其监督。 这就像一场糟糕的比赛。


一切 已经在Java中

实际上,C#也可以解决此问题。 他们选择有颜色。 在他们添加async-await和所有这些Task<T>垃圾之前,您只能使用常规的同步API调用。 其他三种不存在“颜色”问题的语言:Go,Lua和Ruby。


猜猜他们有什么共同点?


流。 或更准确地说:许多可以切换的独立调用堆栈 。 这些不一定是操作系统的线程。 Go中的协程,Lua中的协程以及Ruby中的线程都足够。


(这就是为什么C#需要注意的小部分-您可以通过使用线程来避免C#中的异步痛苦。)


记忆过去的操作


基本的问题是“(异步)操作完成后,如何从同一位置继续”? 您陷入了调用堆栈的深渊,然后调用了某种I / O操作。 为了加速,此操作使用操作系统的基础异步API。 您不能等待它完成。 您必须返回您语言的事件循环,并给操作系统时间完成操作。


一旦发生这种情况,您需要恢复正在做的事情。 通常,该语言通过调用堆栈 “记住它在哪里”。 他将介绍当前已调用的所有功能,并查看每个功能中的命令计数器显示的位置。


但是为了执行异步I / O,您必须展开,丢弃整个C中的调用堆栈。键入Trick-22。 您具有超快速的I / O,但无法使用结果! 所有具有异步I / O的语言(如果是JS,则是浏览器事件循环)都必须以某种方式进行处理。


Node使用他永远正确的行进回调,将所有这些调用填充到闭包中。 当你写:


 function makeSundae(callback) { scoopIceCream(function (iceCream) { warmUpCaramel(function (caramel) { callback(pourOnIceCream(iceCream, caramel)); }); }); } 

这些功能表达式中的每一个都关闭了其整个周围环境。 这会将诸如iceCreamcaramel类的参数从调用堆栈传输到 。 当外部函数返回结果并且调用堆栈被销毁时,这很酷。 数据仍在堆中的某处。


问题是您必须再次复活这些该死的电话。 此转换甚至有一个特殊的名称: 连续传递样式


强大的链接功能

这是70年代语言黑客所发明的,是在编译器支持下使用的一种中间表示形式。 这是一种非常奇怪的方式来引入代码,从而使执行某些编译器优化变得更加容易。


没有人想到程序员可以编写这样的代码 。 然后出现了Node,突然之间,我们所有人都假装编写了一个编译器后端。 我们在哪里弄错了方向?


请注意,承诺和futures实际上并没有太大帮助。 如果使用它们,您将知道仍在堆积巨大的函数表达式层。 您只需将它们传递给.then()而不是异步函数本身即可。


等待生成的解决方案


异步等待确实有帮助。 如果您在编译器遇到await时查看它的await ,您会看到它实际上在执行CPS转换。 这就是为什么您需要在C#中使用await原因 -这是对编译器的提示-“在中间停止函数”。 await之后的所有内容都会成为编译器代表您合成的新功能。


这就是为什么async-await在.NET框架内不需要运行时支持的原因。 编译器将此编译为一系列相关的闭包,它已经知道如何处理。 (有趣的是,闭包也不需要运行时支持。它们被编译成匿名类。在C#中,闭包只是对象。)


您可能想知道我何时提到发电机。 您的语言是否yield ? 然后他可以做一些非常相似的事情。


(我相信生成器和async-await实际上是同构的。在我硬盘的尘土飞扬的角落里,有一段代码仅使用async-await在生成器上实现了游戏循环。)


那我在哪 哦是的 因此,通过回调,promise,async-await和生成器,您最终将采用异步函数并将其分解为一堆存在于堆中的闭包。


您的函数在运行时调用外部。 当事件循环或I / O操作完成时,您的函数将被调用并从原处继续。 但这意味着函数顶部的所有内容应返回。 您仍然需要还原整个堆栈。


这是规则的来源:“您只能从红色功能调用红色功能”。 您必须将整个调用堆栈保存在main()或事件处理程序的闭包中。


调用堆栈实现


但是,使用线程( 绿色或操作系统级别)时,您不需要这样做。 您可以简单地暂停整个线程并跳转到OS或事件循环, 而不必从所有这些函数中返回


以我的理解,Go语言最完美地做到了这一点。 一旦执行任何I / O操作,Go就会停放此协程并继续执行其他不受I / O阻止的操作。


如果您查看Golang标准库中的I / O操作,它们似乎是同步的。 换句话说,它们只是工作,然后在准备就绪时返回结果。 但是这种同步与Javascript并不相同。 Go- , IO . Go .


Go — , . , , .


, API, , . .





, , . , . , 50% .


, , , .


Javascript -, , , JS , JS , . , JS .


, ( ) — , , , async . import threading ( , AsyncIO, Twisted Tornado, ).


, , , , , , .


, Go, Go .


, , , ( - ) , "async-await ". .


, .


, , .

Source: https://habr.com/ru/post/zh-CN466337/


All Articles