Qual a cor da sua função?

NĂŁo sei vocĂȘ, mas para mim nĂŁo hĂĄ melhor começo do dia do que me preocupar com programação. O sangue ferve ao ver uma crĂ­tica bem-sucedida de uma das lĂ­nguas "ousadas" usadas pelos plebeus, atormentada durante todo o dia de trabalho entre visitas tĂ­midas ao StackOverflow.


(Enquanto isso, vocĂȘ e eu usamos apenas a linguagem mais esclarecida e as ferramentas sofisticadas projetadas para as mĂŁos hĂĄbeis de mestres como nĂłs).


Obviamente, como autor do sermĂŁo, corro riscos. VocĂȘ pode gostar do idioma que eu zoo! Um panfleto imprudente poderia ter inadvertidamente levado ao meu blog uma multidĂŁo furiosa de celulares com forquilhas e tochas prontas.


Para me proteger do fogo justo e nĂŁo ofender seus sentimentos (provavelmente delicados), falarei sobre a linguagem ...


... quem acabou de aparecer. Sobre uma efĂ­gie de palha, cujo Ășnico papel Ă© queimar crĂ­ticos em jogo.


Sei que isso parece bobagem, mas acredite, no final, veremos cujo rosto (ou rostos) foi pintado em uma cabeça de palha.


Novo idioma


SerĂĄ um exagero aprender um idioma completamente novo (e chato) apenas para um artigo de blog, entĂŁo digamos que seja muito semelhante ao idioma que jĂĄ conhecemos. Por exemplo Javascript. Chaves e ponto e vĂ­rgula. if , while , etc. - Lingua franca da nossa multidĂŁo.


Eu escolhi JS nĂŁo porque este artigo Ă© sobre ele. É apenas uma linguagem na qual o leitor comum provavelmente entrarĂĄ. Voila:


 function thisIsAFunction(){ return "!"; } 

Como o bicho de pelĂșcia Ă© uma linguagem legal (de leitura ruim), possui funçÔes de primeira classe . EntĂŁo vocĂȘ pode escrever algo como isto:


 //  ,     , //    function filter(collection, predicate) { var result = []; for (var i = 0; i < collection.length; i++) { if (predicate(collection[i])){ result.push(collection[i]); } } return result; } 

Este Ă© um dos recursos de primeira classe e, como o nome sugere, eles sĂŁo legais e super Ășteis. VocĂȘ provavelmente estĂĄ acostumado a transformar coleçÔes de dados com a ajuda deles, mas assim que compreende o conceito, começa a usĂĄ-los em qualquer lugar, caramba.


Talvez nos testes:


 describe("", function(){ it(" ", function(){ expect("").not.toBe(""); }); }; 

Ou quando vocĂȘ precisar analisar (analisar) os dados:


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

Depois de acelerar, vocĂȘ escreve todos os tipos de bibliotecas e aplicativos reutilizĂĄveis ​​que giram em torno de funçÔes, chamadas de funçÔes, retornos de funçÔes - um estande funcional.


tradutor: no original "Functapalooza". O prefixo -a-palooza Ă© tĂŁo legal que vocĂȘ deseja compartilhĂĄ-lo com todos.

Qual a cor da sua função?


E aqui começam as esquisitices. Nossa linguagem tem uma característica peculiar:


1. Cada função tem uma cor.


Cada função - um retorno de chamada anÎnimo ou uma função regular com um nome - é vermelha ou azul. Como o destaque do código em nosso blog não destaca as diferentes cores das funçÔes, vamos concordar que a sintaxe é:


 blue*function doSomethingAzure(){ //   ... } red*function doSomethingCarnelian(){ //    ... } 

Nossa linguagem nĂŁo possui funçÔes incolores. Deseja criar um recurso? - deve escolher uma cor. Estas sĂŁo as regras. E hĂĄ mais algumas regras que vocĂȘ deve seguir:


2. A cor afeta a maneira como a função é chamada


Imagine que existem duas sintaxes para chamar funçÔes - "azul" e "vermelho". Algo como:


 doSomethingAzure(...)*blue; doSomethingCarnelian()*red; 

Ao chamar uma função, vocĂȘ deve usar uma chamada que corresponda Ă  sua cor. Se vocĂȘ nĂŁo adivinhou - eles chamaram a função vermelha com *blue apĂłs os colchetes (ou vice-versa) - algo muito ruim acontecerĂĄ. Um pesadelo esquecido na infĂąncia, como um palhaço com cobras em vez de mĂŁos que se escondia embaixo da sua cama. Ele pularĂĄ do monitor e chuparĂĄ seus olhos.


Regra estĂșpida, certo? Ah, mas mais uma coisa:


3. Somente a função vermelha pode causar a função vermelha.


VocĂȘ pode chamar a função azul do vermelho. Isto Ă© kosher:


 red*function doSomethingCarnelian(){ doSomethingAzure()*blue; } 

Mas nĂŁo o contrĂĄrio. Se vocĂȘ tentar:


 blue*function doSomethingAzure(){ doSomethingCarnelian()*red; } 

- vocĂȘ serĂĄ visitado pelo velho Clown Spider Maw.


Isso torna mais difícil escrever funçÔes mais altas, como filter() do exemplo. Devemos escolher uma cor para cada nova função e isso afeta a cor das funçÔes que podemos passar para ela. A solução óbvia é tornar o filter() vermelho. Então podemos chamar pelo menos funçÔes vermelhas, pelo menos azuis. Mas então nos machucamos com o próximo espinho na coroa de espinhos, que é o idioma indicado:


4. FunçÔes vermelhas causam dor


NĂŁo identificaremos essa "dor", apenas imagine que o programador deve pular o arco toda vez que ele chamar a função vermelha. A chamada pode ser muito polissilĂĄbica ou vocĂȘ nĂŁo pode executar a função em algumas expressĂ”es. Ou vocĂȘ sĂł pode acessar a função vermelha a partir de linhas Ă­mpares.


NĂŁo importa o que seja, mas se vocĂȘ decidir tornar a função vermelha, todos que usarem sua API quererĂŁo cuspir no cafĂ© ou fazer algo pior.


A solução Ăłbvia nesse caso Ă© nunca usar funçÔes vermelhas. Apenas deixe tudo azul e vocĂȘ estarĂĄ de volta ao mundo normal, onde todas as funçÔes sĂŁo da mesma cor, o que Ă© igual ao fato de que elas nĂŁo tĂȘm cor e que nossa linguagem nĂŁo Ă© completamente idiota.


Infelizmente, os sĂĄdicos que desenvolveram essa linguagem (todos sabem que os autores de linguagens de programação sĂŁo sĂĄdicos, certo?) Prendem o Ășltimo espinho em nĂłs:


5. Algumas das principais funçÔes do idioma são vermelhas.


Algumas funçÔes integradas à plataforma, funçÔes que precisamos usar e que não podem ser escritas por nós mesmos, estão disponíveis apenas em vermelho. Nesse ponto, uma pessoa inteligente pode começar a suspeitar que esse idioma nos odeia.


Isso Ă© tudo culpa das linguagens funcionais!


VocĂȘ pode pensar que o problema Ă© que estamos tentando usar funçÔes de ordem superior. Se pararmos de brincar com toda essa bobagem funcional e começarmos a escrever funçÔes azuis de primeira ordem normais (funçÔes que nĂŁo operam com outras funçÔes - aprox. Translator), conforme planejado por Deus -, nos livraremos de toda essa dor.


Se chamarmos apenas funçÔes azuis, tornaremos todas as nossas funçÔes azuis. Caso contrårio, tornamos vermelho. Até criarmos funçÔes que aceitem funçÔes, não precisamos nos preocupar com "polimorfismo com a cor da função" (policromåtico?) Ou com outras bobagens.


Mas, infelizmente, funçÔes de ordem superior são apenas um exemplo. O problema surge toda vez que queremos dividir nosso programa em funçÔes para reutilização.


Por exemplo, temos um pequeno pedaço de código que, bem, não sei, implementa o algoritmo de Dijkstra sobre um gråfico que representa o quanto suas conexÔes sociais pressionam umas às outras. (Passei muito tempo tentando decidir o que o resultado significaria. Indesejabilidade transitiva?)


Mais tarde, vocĂȘ precisou usar esse algoritmo em outro lugar. Naturalmente, vocĂȘ quebra o cĂłdigo em uma função separada. Ligue para ela do lugar antigo e do novo. Mas que cor deve ser a função? VocĂȘ provavelmente tentarĂĄ tornĂĄ-lo azul, mas e se ele usar uma dessas funçÔes "apenas vermelhas" desagradĂĄveis ​​da biblioteca do kernel?


Digamos que o novo local do qual vocĂȘ deseja chamar a função seja azul? Mas agora vocĂȘ precisa reescrever o cĂłdigo de chamada em vermelho. E, em seguida, refaça a função que chama esse cĂłdigo. Ufa VocĂȘ terĂĄ que se lembrar constantemente da cor de qualquer maneira. Esta serĂĄ a areia do seu calção de banho em uma programação de fĂ©rias na praia.


Alegoria da cor


Na verdade, nĂŁo estou falando de cores. Isso Ă© uma alegoria, um artifĂ­cio literĂĄrio. Foda-se - isso nĂŁo Ă© sobre as estrelas nas barrigas , Ă© sobre a corrida. VocĂȘ provavelmente jĂĄ suspeita ...


FunçÔes vermelhas - assíncronas


Se vocĂȘ programar em JavaScript ou Node.js, sempre que definir uma função que chama uma função de retorno de chamada (retorno de chamada) para "retornar" o resultado, vocĂȘ escreverĂĄ uma função vermelha. Veja esta lista de regras e observe como elas se encaixam na minha metĂĄfora:


  1. FunçÔes síncronas retornam um resultado, funçÔes assíncronas não; em troca, chamam um retorno de chamada.
  2. As funçÔes sĂ­ncronas retornam o resultado como um valor de retorno, as funçÔes assĂ­ncronas o retornam, causando o retorno de chamada que vocĂȘ passou para eles.
  3. VocĂȘ nĂŁo pode chamar uma função assĂ­ncrona de uma sĂ­ncrona, porque nĂŁo pode saber o resultado atĂ© que a função assĂ­ncrona seja executada posteriormente.
  4. FunçÔes assĂ­ncronas nĂŁo sĂŁo compiladas em expressĂ”es devido a retornos de chamada, exigem que seus erros sejam tratados de maneira diferente e nĂŁo podem ser usados ​​em um bloco de try/catch ou em vĂĄrias outras expressĂ”es que controlam o programa.
  5. A coisa toda sobre o Node.js é que a biblioteca do kernel é toda assíncrona. (Embora eles doem de volta e começaram a adicionar versÔes _Sync() a vårias coisas).

Quando as pessoas falam sobre "inferno de retorno de chamada" , falam sobre como é irritante ter funçÔes "vermelhas" em seu idioma. Quando eles criam 4089 bibliotecas para programação assíncrona (em 2019, jå em 11217 - aprox. Tradutor), eles tentam lidar com o problema no nível da biblioteca de que estavam presos à linguagem.


Eu prometo que o futuro Ă© melhor


na tradução: "Eu prometo que o futuro Ă© melhor" o jogo de palavras do tĂ­tulo e do conteĂșdo da seção estĂĄ perdido

As pessoas no Node.js hĂĄ muito tempo percebem que os retornos de chamada sĂŁo prejudiciais e estavam procurando soluçÔes. Uma das tĂ©cnicas que inspirou muitas pessoas sĂŁo as promises , que vocĂȘ tambĂ©m pode conhecer pelo apelido de futures .


na TI russa, em vez de traduzir "promessas" como "promessas", foi estabelecido um papel vegetal do inglĂȘs - "promessas". A palavra "Futuros" Ă© usada como Ă©, provavelmente porque os "futuros" jĂĄ estĂŁo ocupados por gĂ­rias financeiras.

Promis Ă© um wrapper para retorno de chamada e manipulador de erros. Se vocĂȘ estĂĄ pensando em passar um retorno de chamada para o resultado e outro retorno de chamada para o erro, o future Ă© a personificação dessa ideia. Este Ă© um objeto bĂĄsico que Ă© uma operação assĂ­ncrona.


Acabei de receber um monte de palavras sofisticadas e pode parecer uma ótima solução, mas principalmente é óleo de cobra . As promessas realmente facilitam a escrita de código assíncrono. Como são mais fåceis de compor em expressÔes, a regra 4 é um pouco menos rigorosa.


Mas, para ser sincero, é como a diferença entre um golpe no estÎmago ou na virilha. Sim, não dói tanto, mas ninguém ficarå encantado com essa escolha.


VocĂȘ ainda nĂŁo pode usar promessas com tratamento de exceção ou outros
operadores de gerenciamento. VocĂȘ nĂŁo pode chamar uma função que retorne future do cĂłdigo sĂ­ncrono. (vocĂȘ pode , mas o prĂłximo mantenedor do seu cĂłdigo inventarĂĄ uma mĂĄquina do tempo, retornarĂĄ no momento em que vocĂȘ o fez e enfiarĂĄ um lĂĄpis na sua cara pelo motivo 2).


As promessas ainda dividem seu mundo em metades assĂ­ncronas e sĂ­ncronas com todo o sofrimento que se segue. Portanto, mesmo que seu idioma suporte promises ou futures , ele ainda se parece muito com o meu idioma de pelĂșcia.


(Sim, isso inclui até o Dart que eu uso. Portanto, estou tão feliz que parte da equipe esteja tentando outras abordagens do paralelismo )


link do projeto abandonado oficialmente

Estou aguardando uma solução


Os programadores de C # provavelmente se sentem complacentes (a razĂŁo pela qual estĂŁo se tornando cada vez mais vĂ­timas Ă© que Halesberg e a empresa polvilham tudo e polvilham a linguagem com açĂșcar sintĂĄtico). Em C #, vocĂȘ pode usar a palavra-chave await para chamar uma função assĂ­ncrona.


Isso torna a realização de chamadas assĂ­ncronas tĂŁo fĂĄcil quanto sĂ­ncrona, com a adição de uma pequena palavra-chave atraente. VocĂȘ pode inserir uma chamada em await nas expressĂ”es, usĂĄ-las no tratamento de exceçÔes, no fluxo de instruçÔes. VocĂȘ pode enlouquecer. Vamos esperar a chuva como dinheiro para o seu novo ĂĄlbum de rapper.


O Async-Waitit Ă© bom, entĂŁo o adicionamos ao Dart. É muito mais fĂĄcil escrever cĂłdigo assĂ­ncrono com ele. Mas, como sempre, hĂĄ um "Mas". Aqui estĂĄ. Mas ... vocĂȘ ainda divide o mundo ao meio. As funçÔes assĂ­ncronas agora sĂŁo mais fĂĄceis de escrever, mas ainda sĂŁo funçÔes assĂ­ncronas.


VocĂȘ ainda tem duas cores. A espera assĂ­ncrona resolve o problema irritante nÂș 4 - eles tornam as funçÔes de chamada em vermelho nĂŁo mais difĂ­ceis do que as chamadas em azul. Mas o restante das regras ainda estĂĄ aqui:


  1. FunçÔes síncronas retornam valores, funçÔes assíncronas retornam um wrapper ( Task<T> em C # ou Future<T> em Dart) ao redor do valor.
  2. SĂ­ncrono apenas chamado, necessidade assĂ­ncrona de await .
  3. Ao chamar uma função assĂ­ncrona, vocĂȘ obtĂ©m um objeto wrapper quando realmente deseja um valor. VocĂȘ nĂŁo pode expandir o valor atĂ© tornar sua função assĂ­ncrona e chamĂĄ-la com await (mas consulte o prĂłximo parĂĄgrafo).
  4. Além de aguardar um pouco de decoração, pelo menos resolvemos esse problema.
  5. A biblioteca principal do C # Ă© mais antiga que a assincronia, entĂŁo eu acho que eles nunca tiveram esse problema.

Async realmente melhor. Eu preferiria espera assĂ­ncrona a retornos nus em qualquer dia da semana. Mas mentimos para nĂłs mesmos se pensarmos que todos os problemas estĂŁo resolvidos. Assim que vocĂȘ começa a escrever funçÔes de ordem superior ou a reutilizar o cĂłdigo, percebe novamente que a cor ainda estĂĄ lĂĄ, sangrando por todo o seu cĂłdigo-fonte.


Qual idioma nĂŁo Ă© cor?


EntĂŁo, JS, Dart, C # e Python tĂȘm esse problema. CoffeeScript e a maioria das outras linguagens compilando tambĂ©m em JS (e Dart herdado). Acho que mesmo o ClojureScript tem essa pegada, apesar de seus esforços ativos com o core.async


Quer saber qual deles nĂŁo? Java Estou certo Com que frequĂȘncia vocĂȘ diz: "Sim, apenas Java estĂĄ fazendo certo"? E assim aconteceu. Em sua defesa, eles estĂŁo tentando ativamente corrigir sua supervisĂŁo promovendo futures e E / S assĂ­ncronas. É como uma corrida pior que pior.


tudo jĂĄ estĂĄ em Java

C #, de fato, tambĂ©m pode contornar esse problema. Eles escolheram ter cor. Antes de adicionarem async-waitit e todo esse lixo eletrĂŽnico Task<T> , era possĂ­vel usar chamadas de API sĂ­ncronas regulares. TrĂȘs outros idiomas que nĂŁo tĂȘm um problema de "cor": Go, Lua e Ruby.


Adivinha o que eles tĂȘm em comum?


Streams. Ou, mais precisamente: muitas pilhas de chamadas independentes que podem ser trocadas . Esses nĂŁo sĂŁo necessariamente threads do sistema operacional. Corotinas em Go, corotinas em Lua e threads em Ruby sĂŁo adequadas.


(É por isso que existe essa pequena advertĂȘncia para C # - vocĂȘ pode evitar a dor assĂ­ncrona em C # usando threads.)


Memória de operaçÔes passadas


O problema fundamental Ă© "como continuar do mesmo local quando a operação (assĂ­ncrona) Ă© concluĂ­da"? VocĂȘ mergulhou no abismo da pilha de chamadas e depois chamou algum tipo de operação de E / S. Por uma questĂŁo de aceleração, esta operação usa a API assĂ­ncrona subjacente do seu sistema operacional. VocĂȘ nĂŁo pode esperar para concluir. VocĂȘ deve retornar ao loop de eventos do seu idioma e dar tempo ao SO para concluir a operação.


Quando isso acontece, vocĂȘ precisa retomar o que estava fazendo. Geralmente, o idioma "lembra onde estava" na pilha de chamadas . Ele segue todas as funçÔes que foram chamadas no momento e olha para onde o contador de comandos em cada uma delas aparece.


Mas, para executar E / S assĂ­ncrona, vocĂȘ deve relaxar, descartar toda a pilha de chamadas em C. Digite Trick-22. VocĂȘ possui E / S super rĂĄpidas, mas nĂŁo pode usar o resultado! Todos os idiomas com E / S assĂ­ncrona sob o capĂŽ - ou, no caso de JS, o loop de eventos do navegador - sĂŁo forçados a lidar com isso de alguma forma.


Node, com seus retornos de chamada marcantes para sempre, enfia todas essas chamadas em fechamento. Quando vocĂȘ escreve:


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

Cada uma dessas expressÔes funcionais fecha todo o contexto circundante. Isso transfere parùmetros, como iceCream e caramel , da pilha de chamadas para a pilha . Quando uma função externa retorna um resultado e a pilha de chamadas é destruída, isso é legal. Os dados ainda estão em algum lugar na pilha.


O problema Ă© que vocĂȘ precisa ressuscitar cada uma dessas chamadas malditas novamente. Existe atĂ© um nome especial para esta conversĂŁo: estilo de passagem de continuação


vincular funcionalidade feroz

Isso foi inventado por hackers de linguagem nos anos 70, como uma representação intermediåria para uso sob o capÎ de compiladores. Essa é uma maneira muito bizarra de introduzir código que facilita a execução de algumas otimizaçÔes do compilador.


Ninguém nunca pensou que um programador pudesse escrever esse código . E então Node apareceu e, de repente, todos fingimos escrever um back-end do compilador. Onde nós viramos o caminho errado?


Observe que promessas e futures realmente nĂŁo ajudam muito. Se vocĂȘ usĂĄ-los, sabe que ainda estĂĄ acumulando camadas gigantes de expressĂ”es funcionais . VocĂȘ apenas os passa para .then() vez da prĂłpria função assĂ­ncrona.


Aguardando uma solução gerada


Async-waitit realmente ajuda. Se vocĂȘ olhar embaixo do capĂŽ para o compilador quando ele encontrar, vocĂȘ verĂĄ que ele realmente realiza a conversĂŁo do CPS. É por isso que vocĂȘ precisa usar await em C # - esta Ă© uma dica para o compilador - "interrompa a função aqui no meio". Tudo o que await depois se torna uma nova função que o compilador sintetiza em seu nome.


É por isso que o async-waitit nĂŁo precisa de suporte de tempo de execução dentro da estrutura .NET. O compilador compila isso em uma cadeia de fechamentos relacionados, com os quais ele jĂĄ sabe como lidar. (Curiosamente, os fechamentos tambĂ©m nĂŁo precisam de suporte de tempo de execução. Eles sĂŁo compilados em classes anĂŽnimas. Em C #, os fechamentos sĂŁo apenas objetos.)


VocĂȘ provavelmente estĂĄ se perguntando quando menciono os geradores. Existe yield no seu idioma? EntĂŁo ele pode fazer algo muito semelhante.


(Acredito que os geradores e o async-waitit são realmente isomórficos. Em algum lugar nos cantos e recantos empoeirados do meu disco rígido existe um pedaço de código que implementa um ciclo de jogo nos geradores usando apenas o async-wait).


EntĂŁo, onde eu estou? Ah sim. Assim, com retornos de chamada, promessas, espera assĂ­ncrona e geradores, vocĂȘ acaba pegando sua função assĂ­ncrona e dividindo-a em vĂĄrios fechamentos que ficam na pilha.


Sua função chama externa em tempo de execução. Quando o loop de eventos ou a operação de E / S Ă© concluĂ­da, sua função Ă© chamada e continua de onde estava. Mas isso significa que tudo no topo da sua função tambĂ©m deve retornar. VocĂȘ ainda precisa restaurar a pilha inteira.


É daĂ­ que vem a regra: "VocĂȘ sĂł pode chamar a função vermelha a partir da função vermelha" VocĂȘ deve salvar toda a pilha de chamadas nos fechamentos para main() ou para o manipulador de eventos.


Implementação da pilha de chamadas


Mas, usando threads ( verde ou nĂ­vel do SO), vocĂȘ nĂŁo precisa fazer isso. VocĂȘ pode simplesmente pausar o segmento inteiro e pular para o SO ou o loop de eventos sem precisar retornar de todas essas funçÔes .


A linguagem Go, no meu entender, faz isso da maneira mais perfeita. Assim que vocĂȘ fizer qualquer operação de E / S, o Go estacionarĂĄ essa corotina e continuarĂĄ qualquer outra que nĂŁo seja bloqueada pela E / S.


Se vocĂȘ observar as operaçÔes de E / S na biblioteca padrĂŁo Golang, elas parecem sĂ­ncronas. Em outras palavras, eles simplesmente funcionam e retornam o resultado quando estiverem prontos. Mas essa sincronização nĂŁo significa o mesmo que no 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/pt466337/


All Articles