你的功能是什么颜色的?

2020-05-19 13:05:06

我不知道你怎么想,但是没有什么比一种老式的编程语言咆哮更能让我早上行动起来了。看到有人使用平民使用的那种“鲸鱼”语言,在偷偷访问StackOverflow的间隙蒙混过关,这会激起人们的愤怒。

(同时,你和我只使用最开明的语言。(为像我们这样的专业工匠修剪指甲的手设计的凿子工具。)。

当然,作为Said Screed的作者,我有风险。我模仿的语言可能是你喜欢的语言!在不知不觉中,我本可以让乌合之众开刀博客、干草叉和火炬随时准备好,而我那愚蠢而顽强的小册子可能会招致他们的愤怒!

为了保护自己不受那些火焰的灼热,也为了避免冒犯你可能脆弱的情感,我会咆哮着说一种我刚刚编造的语言。一个唯一目的就是要被点燃的稻草人。

我知道,这看起来毫无意义,对吧?相信我,到最后,我们会看到谁的脸(或几张脸!)。画在他的稻草头上。

仅仅为了一篇博客文章而学习一门全新的(糟糕的)语言是一项艰巨的任务,所以让我们假设它与你我已经知道的语言大致相似。我们会说它的语法有点像JS。花括号和分号。如果,如果,等等。编程洞穴的通用语。

我选择JS并不是因为这篇文章就是关于这个的。这只是你的语言,普通读者的统计代表,最有可能理解。瞧啊:

因为我们的稻草人是一门现代(差劲)的语言,所以我们也有一流的功能。因此,您可以制作类似于以下内容的内容:

//返回包含集合中//所有匹配谓词的元素的列表。函数筛选器(集合,谓词){var result=[];for(var i=0;i<;集合。长度;i++){if(谓词(Collection[i])结果。Push(Collection[i]);}返回结果;}。

这是那些高阶函数中的一个,顾名思义,它们是优雅的,因为它们都出来了,而且非常有用。你可能已经习惯了他们到处摆弄藏品,但是一旦你内化了这个概念,你几乎在任何地方都会开始使用它们。

所以你到镇上去,写各种很棒的可重用的库和应用程序,传递函数,调用函数,返回函数,Funcapalooza。

除了等待。这就是我们的语言变得古怪的地方。它有一个独特的特点:

每个函数-匿名回调或常规命名函数-要么是红色的,要么是蓝色的。由于我的博客的代码高亮笔不能处理实际颜色,我们可以说其语法如下:

Blue·function doSomethingAzure(){//这是一个蓝色函数.。}red·函数doSomethingCarnelian(){//这是一个red函数.。}。

语言中没有毫无意义的功能。想做个函数吗?得选个颜色。这是规则。而且,实际上,你还必须遵循几条规则:

想象一下“蓝色呼叫”语法和“红色呼叫”语法。类似于:

调用函数时,需要使用与其颜色相对应的调用,如果调用错误-在括号后使用·Blue调用红色函数,反之亦然-它会做一些不好的事情。从你的童年中挖出一些被遗忘已久的噩梦,就像一个躲在床下的以蛇为臂的小丑。它会从你的监视器里跳出来,吸走你的玻璃体幽默感。

但你不能走另一条路。如果您尝试这样做:

这使得编写像我们的filter()示例这样的高阶函数变得很困难,我们必须为它选择一种颜色,这会影响我们被允许传递给它的函数的颜色。显而易见的解决方案是将filter()设置为红色,这样它就可以接受红色或蓝色函数并调用它们。但随后我们进入了下一个令人发痒的地方,那就是这种语言:

现在,我不会精确地定义“痛苦”,但是可以想象一下,程序员每次调用一个红色函数都要经历一些烦人的圈套。也许它真的很冗长,或者也许你不能在某些类型的语句中做到这一点。也许你只能用质数的线号呼叫他们。

重要的是,如果您决定将一个函数设置为红色,每个使用您的API的人都会想要在您的咖啡里吐口水和/或在里面放一些味道更差的液体。

那么显而易见的解决方案就是永远不要使用RED函数。只要把所有东西都变成蓝色,你就会回到所有函数都有相同颜色的理智世界,这相当于它们都没有颜色,这就相当于我们的语言并不完全愚蠢。

唉,虐待狂的语言设计师-我们都知道所有的编程语言设计师都是虐待狂,不是吗?-戳到了我们身边的最后一根刺:

有一些内置到平台中的功能,我们需要使用的功能,我们无法自己编写,只能用红色表示。在这一点上,一个通情达理的人可能会认为语言恨我们。

你可能会认为这里的问题是我们试图使用更高阶的函数。如果我们不再穿着那些功能繁琐的东西到处乱跑,而是像上帝希望的那样编写正常的蓝领第一级函数,我们就不会让自己感到所有的心痛。

如果我们只调用蓝色函数,请将我们的函数设为蓝色。只要我们从来不制作接受函数的函数,我们就不必担心试图“多态而不是函数色”(多色?)。或者诸如此类的胡说八道。

但是,唉,高阶函数只是一个例子。每当我们想要将程序分解成可重用的独立功能时,这个问题就普遍存在。

例如,假设我们有一个很好的小代码块,我不知道,它在一个图表上实现了Dijkstra的算法,它代表了你的社交网络对彼此的挤压程度。(我花了太长时间试图决定这样的结果到底代表着什么。传递性不受欢迎?)。

稍后,您最终需要在其他地方使用相同的代码块。你做一件很自然的事情,然后把它提升到一个单独的功能中。您可以从旧位置和使用它的新代码中调用它。但它应该是什么颜色呢?显然,如果可以的话,您会将其设置为蓝色,但是如果它使用的是仅支持红色的核心库函数之一,那该怎么办呢?

如果你想叫它蓝色的新地方怎么办?您必须将其设置为红色。然后,您必须将调用它的函数设置为红色。呃.。不管怎样,你都要经常考虑颜色。它将是你泳衣中的沙子,在发展的海滩度假。

当然,我说的不是真的颜色,对吗?这是一种寓言式的文学把戏。Sneetch不是关于肚皮上的星星,而是关于种族。到目前为止,你可能已经对颜色的实际含义有了一个大概的了解。如果不是,这里有个大发现:

如果您在Node.js上使用JavaScript编程,则每次通过调用回调定义一个“返回”值的函数时,您只需创建一个红色函数即可。回过头来看看该规则列表,看看我的比喻是如何堆叠起来的:

同步函数将其结果作为返回值提供,异步函数通过调用您传递给它的回调来提供结果。

您不能从同步函数调用异步函数,因为在异步函数稍后完成之前,您无法确定结果。

由于回调,异步函数不在表达式中组合,具有不同的错误处理,并且不能与try/catch一起使用,也不能在许多其他控制流语句中使用。

节点的全部特点是核心库都是异步的。(尽管他们确实回拨了,并开始添加很多东西的_Sync()版本。)。

当人们谈论“回调地狱”时,他们谈论的是在他们的语言中有红色函数是多么烦人。当他们创建4089个用于异步编程的库时,他们试图在库级处理语言强加给他们的问题。

Node社区的人们已经意识到回调是一种长期的痛苦,并四处寻找解决方案。一种让很多人兴奋的技巧是承诺,你可能也知道他们的说唱歌手名字“未来”。

如果你把回调和错误回调作为一个概念传递给函数,那么承诺基本上就是这个概念的具体化。它是代表异步操作的一级对象。

我在那段话里塞进了一堆花哨的PL语言,所以这听起来可能是一笔不错的交易,但基本上都是蛇油。承诺确实会让异步代码更容易编写。他们的作文要好一些,所以规则4不是很繁琐。

但是,老实说,这就像是在内脏被打一拳和在私处被打一拳之间的区别。是的,没有那么痛苦,但我认为任何人都不应该真正对价值主张感到兴奋。

您仍然不能将它们与异常处理或其他控制流语句一起使用。您仍然不能调用从同步代码返回未来的函数。(嗯,你可以,但是如果你这样做了,后来维护你代码的人会发明一台时光机,回到你做这件事的那一刻,然后用2号铅笔戳你的脸。)。

你仍然把你的整个世界分成异步和同步的两半,以及由此带来的所有痛苦。所以,即使你的语言以承诺或未来为特色,它的脸看起来和我的稻草人上的很像。

(是的,这意味着甚至连我使用的语言DART也是如此。这就是为什么我对团队中的一些人正在试验其他并发模型感到非常兴奋的原因。)。

C#程序员现在可能感到相当自鸣得意(随着Hejlsberg和他的公司在语言中添加了一个又一个可爱的特性,他们越来越容易成为这种情况的牺牲品)。在C#中,您可以使用waitkeyword调用异步函数。

这使您可以像同步调用一样轻松地进行异步调用,只需添加一个可爱的小关键字即可。您可以将等待调用嵌套在表达式中,在异常处理代码中使用它们,将它们填充到内部控制流中。发疯吧。让天下雨,等待电话,就像他们是你为你的新说唱专辑预付的美元一样。

异步等待很不错,这就是为什么我们要把它添加到DART中。这使得编写异步代码变得更加乏味。你知道“但是”就要来了。它是。但是…。你仍然把世界一分为二。这些异步函数更容易编写,但它们仍然是异步函数。

你还有两种颜色。异步等待解决了恼人的第4条规则:它们使redfunction调用起来并不比调用Blue函数差多少。但是所有其他的规则都还在那里:

同步函数返回值,异步函数返回值周围的Task<;T>;(或在DART中为Future<;T>;)包装器。

如果你调用一个异步函数,当你真正想要T的时候,你就得到了这个包装器对象。你不能解开它,除非你把你的函数设为异步的并等待它。(但请参阅下面的内容。)。

C#的核心库实际上比异步更老,所以我猜他们从来没有这个问题。

这样好多了。我将在一周中的任何一天采取异步等待的方式进行赤裸裸的回调或期货交易。但是如果我们认为我们所有的麻烦都在一起,那我们就是在自欺欺人。一旦您开始尝试编写高阶函数或重用代码,您就会马上意识到颜色仍然存在,您的代码库中到处都是血。

所以JS、DART、C#和Python都有这个问题。CoffeeScript和大多数其他编译为JS的语言也是如此(这就是Dart继承它的原因)。我认为即使是ClojureScript也有这个问题,尽管他们已经非常努力地用他们的core.async东西来反对它。

想知道不是这样的吗?爪哇。我知道,对吧?您多久会说一次,“是的,Java才是真正做对这件事的那个。”不过,这就对了。在他们的辩护中,他们正积极尝试通过转向期货和异步IO来纠正这种疏忽。这就像是一场向下的赛跑。

C#实际上也可以避免这个问题。在添加异步等待和所有Task<;T>;内容之前,您只需使用常规的同步API调用。还有三种语言没有这个问题:Go、Lua和Ruby。

螺纹。或者,更准确地说:多个独立的调用栈,可以在它们之间切换。它们不一定要是操作系统线程。GO中的Goroutine、Lua中的协程和Ruby中的Fibre完全够用。

(这就是C#有这个小小的警告的原因。您可以通过使用线程来避免C#中异步的痛苦。)。

根本的问题是“当操作完成时,如何从您停止的地方继续”?您已经构建了一些大的调用堆栈,然后调用了一些IO操作。为了提高性能,该操作使用操作系统的底层异步API。你不能等待它完成,因为它不会完成。你必须一直返回到你的语言的事件循环,给操作系统一些时间来旋转,然后才能完成。

一旦是这样,你就需要恢复你正在做的事情。语言“记住自己所在位置”的通常方式是调用堆栈。它跟踪当前正在调用的所有函数以及指令指针在每个函数中的位置。

但是要执行异步IO,您必须展开丢弃整个C调用堆栈。有点像第22条军规。你可以做超快的IO,但你不能对结果做任何事情!每种具有异步IO的语言-或者在JS的情况下,浏览器的事件循环-都以某种方式应对这一问题。

这些函数表达式中的每一个都结束于其周围的所有上下文。这会将冰激凌和焦糖等参数从堆栈移到堆中。当外部函数返回并且调用堆栈失效时,就很酷了。该数据仍然在堆中浮动。

问题是,您必须手动实现这些步骤中的每一个步骤,这个转换实际上有一个名称:延续传递风格(Continue-PassingStyle)。它是由语言黑客在70年代发明的,作为一种中间表示,用在他们的编译器的内部。这是一种非常奇特的代码表示方式,恰好可以使一些编译器优化更容易完成。

从来没有人想过程序员会写出这样的代码。然后,Node出现了,突然之间,我们在这里假装成编译器后端。我们哪里出错了?

请注意,承诺和期货实际上也不会给你买到任何东西。如果您使用过它们,您就知道您仍然在手工创建大量的函数文字。您只需将它们传递给.Then(),而不是传递给异步函数本身。

异步等待确实有帮助。如果您打开编译器的头颅,看看当它遇到等待调用时它在做什么,您会看到它实际上正在进行CPS转换。这就是为什么您需要在C#中使用aWait:这是编译器说“在这里将函数拆成两半”的提示。等待之后的一切都被提升到一个新函数中,它代表您合成该函数。

这就是异步等待在.NETframework中不需要任何运行时支持的原因。编译器将其编译成一系列它已经可以处理的链式闭包。(有趣的是,闭包本身也不需要运行时支持。它们被编译成匿名类。在C#中,闭包确实是穷人的对象。)。

你可能想知道我什么时候带发电机来。你的语言有没有Year关键字?那么它就可以做一些非常相似的事情。

(事实上,我认为生成器和异步等待是同构的。(我在硬盘的某个黑暗角落里漂浮着一小段代码,它只使用异步等待就实现了生成器风格的游戏循环。)。

我刚才说到哪里?哦,对了。因此,使用回调、承诺、异步等待和生成器,您最终会获得异步函数并将其涂抹到堆中的一堆闭包中。

您的函数将最外层的函数传递到运行库。当事件循环Orio操作完成时,它会调用该函数,您可以从您离开的地方继续。但这意味着你上面的一切也必须归还。你还得把整叠东西解开。

这就是“RED函数只能由RED函数调用”规则的来源。您必须关闭整个调用堆栈,直到返回main()或事件处理程序。

但如果您有线程(绿色或操作系统级别),则不需要这样做。您只需挂起整个线程并直接跳回操作系统或eventloop,而不必从所有这些函数返回。

在我看来,围棋是做这件事最漂亮的语言。一旦您执行任何IO操作,它就会驻留该Goroutine,并恢复IO上未阻塞的任何其他操作。

如果您查看标准库中的IO操作,您会发现它们似乎是同步的。换句话说,他们只是做工作,然后在做完后返回结果。但这并不意味着它们在JavaScript中是同步的。其他GO代码可以在其中一个操作挂起时运行。这是因为GO消除了同步和异步代码之间的区别。

Go中的并发性是您选择如何对程序建模的一个方面,而不是标准库中每个函数的颜色。这意味着我上面提到的五条规则的所有痛苦都被完全和彻底地消除了。

所以,下次你开始告诉我一些新的热门语言,它的并发故事是多么棒,是因为它有异步API,现在你就知道我为什么开始咬牙切齿了。因为这意味着你又回到了红色和蓝色。