Julia: Proposta: Descontinuar e remover a tubulação de função

Criado em 30 jan. 2017  ·  37Comentários  ·  Fonte: JuliaLang/julia

Proposta

Descontinuar o uso atual de |> como um canal de função. Ou seja, a sintaxe x |> f seria preterida em favor da sintaxe de chamada normal f(x) . Após o período de descontinuação, Base.:(|>) ficaria indefinido.

Essa alteração foi inicialmente sugerida por tkelman em https://github.com/JuliaLang/julia/issues/16985#issuecomment -227015399.

Tem havido muito debate contencioso sobre várias sintaxes para tubulação de função (em particular, veja #5571), com argumentos para imitar uma variedade de linguagens. Essa discussão foi _ad nauseum_ e eu não desejo repeti-la. Esse NÃO é o objetivo desta proposta.

Justificativa

Vários pacotes bem pensados ​​e bem mantidos implementaram macros que fornecem sintaxe de tubulação conveniente para uma variedade de casos de uso, gerais e específicos. Exemplos incluem Lazy.jl , FunctionalData.jl , Pipe.jl e ChainMap.jl , entre outros.

StefanKarpinski e andyferris nos deram composição de função arbitrária em #17155, que pode servir a um propósito semelhante em muitas situações.

Como tkelman argumentou de maneira semelhante em #5571, o pipeline de funções em Base está invertido em relação à sintaxe de chamada familiar; ter ambos na linguagem Base é essencialmente endossar o uso de 2 sintaxes diferentes para atingir o mesmo objetivo. Embora muitas vezes existam várias maneiras de escrever a mesma coisa usando soluções no Base, normalmente as soluções pelo menos seguem um modelo mental semelhante. Nesse caso, as sintaxes empregam modelos mentais literalmente opostos.

Os pipelines de função violam o princípio da menor surpresa ao aplicar a ação após o objeto. Ou seja, se você ler sum(x) você saberá imediatamente ao ver sum() que irá somar os valores no argumento. Quando você vê x |> sum , você vê x , então de repente você está somando seus valores. Poucas outras soluções Base colocam a ação no final, o que torna a saída estranha.

Piping realmente tem precedentes em outras linguagens, por exemplo, o %>% de Hadley Wickham em R (que não faz parte da base R), e às vezes esse estilo/fluxo faz sentido. No entanto, no interesse da consistência dentro da Base Julia, proponho que adiemos a responsabilidade de fornecer sintaxe de tubulação aos pacotes, que podem redefinir |> ou fornecer macros de conveniência conforme acharem adequado.

Itens de ação

Caso esta proposta seja aceita, os itens de ação seriam:

  • [ ] Remova os usos da sintaxe dentro do Base, se houver
  • [ ] Fornecer uma suspensão de uso formal para Base.:(|>) em 0,6 ou 1,0
  • [ ] Remova-o em uma versão subsequente
deprecation design julep

Comentários muito úteis

Se estamos descontinuando isso, devemos também descontinuar * para concatenação de strings? Isso tem problemas semelhantes, pois é redundante com string(a, b) , e viola o princípio da menor surpresa, pois a e b não são números.

De maneira mais geral, provavelmente deveríamos descontinuar todas as notações infixas, pois é confuso ter várias convenções de chamada como *(a, b) vs a * b – podemos reduzir nossas 3 sintaxes díspares atuais para uma e obter consistência total. Para evitar a feiúra, podemos considerar mover a chamada de função para dentro dos parênteses e, talvez, livrar-nos das vírgulas redundantes também.

Todos 37 comentários

A tubulação de função fornece uma sintaxe pós-fixada para chamada de função, que é conveniente no REPL para geração de dados interativos e visualização/resumo adicional.

Um caso de uso que eu vi muitas pessoas digitarem é

julia> somecomplicatedthingproducingarray
...

<ARROW UP>

julia> somecomplicatedthingproducingarray |> summarize

onde a função summarize é algo como um gráfico ou histograma

@jiahao Não estou argumentando que não é útil, mas sim que devemos ser consistentes no Base e permitir que os pacotes forneçam coisas assim.

há também ans para uso de reposição

Nesta proposta, |> ainda seria analisado como um operador infixo?

@ajkeller34 : definitivamente, os pacotes seriam livres para fazer o que quiserem com ele (embora eles tenham que jogar bem uns com os outros em termos de pirataria e coexistência de tipos), sem muita restrição de serem semanticamente compatíveis com o antigo definição básica.

Remova os usos da sintaxe no Base, se houver

Aqui está uma tentativa agora muito desatualizada que fiz para fazer isso: https://github.com/tkelman/julia/commit/212727cdc4aaa3221763580f15d42cfe198bcc1c
Na época, a maioria dos usos na base eram bastante triviais. Alguns dos usos dos testes de "encaminhar esta coisa para esta função anônima" talvez sejam melhores com tubulação, mas como a maioria deles estava reutilizando a mesma função anônima várias vezes, provavelmente valeria a pena dar um nome e chamá-la como um função normal nesse ponto.

Caso alguém esteja curioso, tenho o ChainRecursive.jl disponível agora. Vou colocar um anúncio no discurso sobre a desintegração do ChainMap.jl e seus vários filhos assim que estiver completo.

Deixe-me oferecer alguma resistência aqui, pois tenho algum interesse e um gosto particular pelo que |> torna possível.

Eu concordo com @jiahao que |> é muito útil quando você quer testar rapidamente as coisas no REPL. Além disso, também acho útil quando seu argumento é muito grande ou merece algum equilíbrio (sim, eu disse isso) . No caso do exemplo vinculado, é melhor ter o argumento mais proeminente do que a função que está sendo chamada. sum(x) é um exemplo muito simples e deve ser escrito como sum(x) ). Em Escher.jl todas as funções que adicionam propriedades a elementos possuem um método curried. Isso se encaixa tão bem com |> (isso foi planejado, também funciona muito bem com map ) e é uma alegria poder experimentar as coisas no final da linha e ver a atualização da interface do usuário imediatamente. Eu não tenho que encontrar meu caminho para o início da expressão e ficar me enrolando. Para uso com Escher, pelo menos, a alternativa sugerida é atribuir grandes expressões a variáveis ​​de nomes inventados como padded_box_contents_aligned_right_tomato_background (ou pior box34 ) e então chamar uma função nelas. Ao contrário da bela leitura <big UI expression> |> aligncontents(right) |> pad(1em) |> fillcolor("tomato")

Eu sei que depois disso eu posso definir |> dentro do Escher e provavelmente vou, mas vai matar meu cérebro ver WARNING: using Escher.|> in module YourPackage conflicts with an existing identifier. Os pacotes quase definitivamente darão significados diferentes para isso, o que para mim é muito alarmante!

StefanKarpinski e andyferris nos deram composição de função arbitrária em #17155, que pode servir a um propósito semelhante em muitas situações.

A alternativa para box |> fill("orange") |> pad(2em) seria (fill("orange") ∘ pad(2em))(box) em oposição a box |> fill("orange") ∘ pad(2em) ? Estes dois parecem ortogonais.

O uso de closures por Escher como objetos me parece que está definindo uma DSL apenas para usar essa sintaxe (que tem sérias limitações para qualquer coisa que não seja de entrada única, saída única), onde provavelmente seria melhor servido , e mais generalizável, se usasse uma das várias macros de encadeamento disponíveis.

Remover a definição da Base para isso permitiria que as pessoas que gostam dessa sintaxe fizessem coisas mais interessantes com ela.

@shashi Eu entendo seus pontos, mas você seria capaz de obter o mesmo comportamento usando um dos pacotes que citei no problema, não é? Como exemplo, no seu exemplo Escher, você pode usar FunctionalData para fazer <strong i="6">@p</strong> vbox(<really big thing>) | pad(2em) ou Lazy para fazer @> vbox(...) pad(2em) .

Remover a definição da Base para isso permitiria que as pessoas que gostam dessa sintaxe fizessem coisas mais interessantes com ela.

Exceto que não será utilizável, pois a única maneira segura de usá-lo seria Escher.|>(...) ou Lazy.|>(...) .

Hipoteticamente, como alguém usaria |> como um operador infixo se você estivesse usando dois pacotes diferentes que o definem e exportam, supondo que não esteja definido em Base ?

@kmsquire Depende do caso de uso. |> ainda seria analisado como um operador infixo como é agora, apenas não teria um valor em Base. Se você usá-lo em uma macro, não importa como qualquer pacote específico o defina, pois ele simplesmente se torna o primeiro argumento em uma expressão de chamada.

Por exemplo <| , que é analisado como um operador infixo, mas não possui um valor. Mesmo que seja indefinido, ainda temos

julia> dump(:(a <| b))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol <|
    2: Symbol a
    3: Symbol b
  typ: Any

Os pacotes podem definir e exportar métodos para Base.:(<|) que significam coisas diferentes, assim como se pode fazer com + .

Mas os pacotes que fornecem uma boa tubulação de função o fazem em macros, presumo exatamente por esse motivo.

FWIW, nenhum pacote de encadeamento precisaria usar |> durante a avaliação porque durante o encadeamento tudo é compactado em uma expressão. Eu imagino que se os pacotes forem definindo |> será precisamente a definição na base. Embora eles provavelmente devam usar uma macro de encadeamento. Veja DataFramesMeta para um bom exemplo de como construir uma interface que funcione bem com encadeamento.

Se estamos descontinuando isso, devemos também descontinuar * para concatenação de strings? Isso tem problemas semelhantes, pois é redundante com string(a, b) , e viola o princípio da menor surpresa, pois a e b não são números.

De maneira mais geral, provavelmente deveríamos descontinuar todas as notações infixas, pois é confuso ter várias convenções de chamada como *(a, b) vs a * b – podemos reduzir nossas 3 sintaxes díspares atuais para uma e obter consistência total. Para evitar a feiúra, podemos considerar mover a chamada de função para dentro dos parênteses e, talvez, livrar-nos das vírgulas redundantes também.

|> ainda seria analisado como um operador infixo como é agora, apenas não teria um valor em Base. Se você usá-lo em uma macro, não importa como qualquer pacote específico o defina

Ainda não tenho certeza por que precisamos removê-lo da Base.

@bramtayl faz um bom ponto:

Eu imagino que se os pacotes forem definindo |> será precisamente a definição que é base.

E ainda a única maneira de usar mais de um pacote que define isso é não usá-lo infix.

Não vejo por que é necessário remover a definição em Base para que |> seja usado dentro de macros.

Não é. Meu ponto é que |> pode ser usado dentro de macros independentemente da situação na Base. O mesmo vale para qualquer operador que analise adequadamente. O objetivo da proposta é tornar a Base autoconsistente em termos de chamadas de função, então o comportamento da tubulação pode ser alcançado através de pacotes. Se os pacotes usam |> em particular, não importa; eles poderiam usar <| ou literalmente qualquer outro operador infixo.

@ararslan certo, não era isso que eu queria perguntar, atualizei meu comentário logo em seguida, desculpe.

De qualquer forma, não entendo muito bem o sentimento "Base independente em termos de chamadas de função". Parece que isso só tornará mais difícil usar |> em um contexto não macro. Eu pessoalmente acredito que |> é algo que vale a pena aprender para um novato, apesar de ser surpreendente. Pelo menos economiza esforço no REPL. É muito divertido perceber mais tarde que |> é uma função como qualquer outra função infixa e reforça a lição de que funções são apenas valores.

Talvez apenas algo formal: Por favor, discuta/decida sobre depreciações e/ou mudanças de sintaxe no início de um ciclo de lançamento, não no final. Atualmente todos os principais desenvolvedores e responsáveis ​​pelos pacotes gastam tempo e energia para terminar o 0.6 e eles podem não ter tempo para pensar em outra (boa) ideia.

"Não estou argumentando que não é útil, mas sim que devemos ser consistentes"

Às vezes, a utilidade supera a consistência? Eu não estava ciente da inconsistência, mas achei a sintaxe |> útil. Se for removido, não sentirei que ganhei nada tangível.

Uma explicação para o meu voto negativo, se me permite:

Muito do que está atualmente no Base pode acontecer em pacotes. Devemos mover dicionários para um pacote? Talvez listar operações como classificar e embaralhar? Operações de cobrança, etc.? Tenho certeza de que houve discussões longas e detalhadas sobre o que deve e o que não deve ser incluído no Base, mas presumo que haja três razões pelas quais algumas funcionalidades podem ser incluídas no base:
1) Essa funcionalidade é necessária para habilitar outros recursos na base.
2) Essa funcionalidade é uma parte essencial da linguagem, muitos programadores e pacotes Julia irão usá-la e, portanto, é desejável ter uma única implementação/sintaxe com a qual todos concordem, em vez da fragmentação de muitas pessoas rolando suas próprias.
3) Incluir essa funcionalidade na base torna a Julia "crua" mais agradável de usar ou faz com que pareça mais cheia de recursos, o que ajuda no evangelismo e na adoção da linguagem.

Algo como sum provavelmente atinge todos os 3 pontos, e eu diria que a tubulação de função atinge o segundo e o terceiro ponto:

Tanto na proposta inicial (bem escrita) quanto na discussão neste tópico, um tema comum é a existência de vários pacotes que fornecem funcionalidade semelhante a um pipe por meio de macros: Lazy.jl, Pipe.jl, ChainMap.jl, etc. A existência de vários pacotes sugere fortemente que muitas pessoas na comunidade consideram o pipe um recurso útil e desejável, e a presença desses pacotes neste tópico de discussão sugere que muitas pessoas aqui entendem e apoiam o uso do pipe.

Dado que o pipe é um recurso comum e popular na comunidade Julia e em outras linguagens, mesmo nesta discussão as pessoas parecem concordar que ele tem muitos usos, especialmente no REPL (onde Julia brilha), e já existe fragmentação no ecossistema Julia. ..minha leitura não é que ele deve ser removido do Base, mas sim que a sintaxe de tubulação disponível no Base deve ser aprimorada para que haja menos necessidade de fragmentação. Diferentes pacotes que oferecem diferentes formas de plotagem, por exemplo, parecem bons; diferentes pacotes populares que oferecem diferentes maneiras de aplicar funções parece bastante assustador.

Argumento ainda que remover a tubulação da Base, mas deixar o operador infixo é bastante surpreendente: em Julia você não pode definir seus próprios operadores infixos, mas há um operador infixo não utilizado |> que você pode definir como Você por favor? Se isso é uma boa funcionalidade, por que não nos dar um sólido 10 ou 20 operadores infixos para definir como quisermos?

Por fim, acredito que seja natural manter a tubulação exatamente porque é diferente de outra aplicação de função. É um recurso, não um bug, que é diferente de outras convenções de aplicação de funções; essa diferença é o que faz brilhar em alguns casos de uso. E há outros casos em que (acenando um pouco com a mão) o substantivo vem antes do verbo, e muitos deles são exatamente açúcar sintático nos casos em que a aplicação da função bruta é difícil de manejar. De cabeça, a atribuição x = 5 é colocar o substantivo (símbolo x ) antes do verbo (vincular a um valor). Da mesma forma para acessar campos do tipo t.a em vez de getfield . E mais profundamente, a indexação de array z[5] se lê como "de z pegue o 5º elemento" e geralmente é mais natural do que getindex(z, 5) .

Se isso é uma boa funcionalidade, por que não nos dar um sólido 10 ou 20 operadores infixos para definir como quisermos?

Provavelmente há mais do que isso se incluirmos todos os unicode, além dos ASCII não reivindicados, como <| , ++ , ...

Não estou lendo o tópico inteiro - mas só queria dizer
que eu amo ser capaz de canalizar. Eu votaria útil sobre
consistência qualquer dia.

Eu tenho uma preferência muito leve para mantê-lo, mas realmente não me importo, desde que permaneça um operador infixo. Eu sinto que provavelmente não usaria tubulação de função se isso envolvesse a importação de um pacote, o que me diz que eu não o valorizo ​​muito.

Dito isto, não acho que esse argumento do "princípio da menor surpresa" seja convincente, pois faz algumas suposições sobre uma base de usuários diversificada. Para falantes nativos de linguagens sujeito-objeto-verbo, suponho que a maior parte da sintaxe de Julia viola o princípio da menor surpresa, e a tubulação de função é bastante confortável ...

Não está lendo o tópico inteiro

😕

Eu amo poder canalizar

Novamente, não estou argumentando que não se deve ser capaz de canalizar, mas sim que a funcionalidade poderia ser facilmente obtida em um dos vários pacotes de canalização existentes. A remoção do tubo Base permite que os pacotes definam mais facilmente sua própria semântica de tubulação sem ter que aderir ou permanecer consistente com o que o Base fornece.

em Julia você não pode definir seus próprios operadores infixos

Isso não é verdade; qualquer coisa que seja analisada como um operador infixo pode ser definida ou redefinida. Como os martinholters apontaram, <| e ++ estão igualmente disponíveis, entre outros.

Eu sou meio neutro sobre isso, mas vou apoiar o sentimento de que |> estar para trás da sintaxe de chamada de função normal é o ponto principal disso. Mesmo os maiores fãs de tubulação não estão pedindo (AFAIK) por exemplo sin <| x porque isso realmente é redundante com sin(x) . |> é para aqueles casos em que é mais fácil para os olhos e/ou cérebro pensar em dados fluindo da esquerda para a direita sem muitos parênteses.

Eu gostaria que |> fosse mais poderoso, por exemplo, permitisse x |> f(_) + 2g(_) |> h etc. e que não fosse apenas um operador. Toda vez que alguém definiu x |> f para significar algo além f(x) , isso realmente me engana porque o objetivo do operador como o usamos é que é uma sintaxe de chamada de ordem diferente. Como podemos sobrecarregar a chamada, não vejo uma boa razão para x |> f significar outra coisa.

@StefanKarpinski Pipes mais poderosos já podem ser obtidos usando macros. Veja, por exemplo, Pipe.jl , que fornece exatamente a sintaxe que você está descrevendo. Contanto que |> seja um operador (eu pessoalmente não vejo |> como um caso especial), as macros podem usar qualquer delimitador de tubulação que analise infixo, mesmo que não seja um :call . Como exemplo, pode-se usar @~ para canalizar (pelo menos até o momento em que este artigo foi escrito). Esse nível de flexibilidade é uma das vantagens de usar macros em Julia.

Poderíamos adicionar a funcionalidade do Pipe.jl à linguagem, e então você a teria sem precisar escrever @pipe .

A principal razão para descontinuar |> seria se quisermos recuperar a sintaxe para algum outro propósito que as pessoas gostem muito mais.

Acho que estou tentando argumentar que a tubulação não precisa fazer parte da linguagem, ela pode (e já faz) viver em um pacote.

Mas se não há mais nada para o qual queremos |> , vejo pouco mal em deixar sua definição (trivial) em paz.

Não acredito que existam atualmente propostas para reaproveitar |> na Base. Meu argumento para não defini-lo em Base é que nos dá mais consistência sem perda de funcionalidade.

Alguma proposta de "tubulação mais poderosa" ou implementações de pacotes seria simplificada por não ter essa definição existente para se preocupar ou contornar?

@ararslan "Isso não é verdade; qualquer coisa que seja analisada como um operador infixo pode ser definida ou redefinida."

Do manual "&& e || operadores", eles são analisados, mas não podem ser redefinidos (é uma coisa boa). Acredito que as únicas exceções.

Os chamados "operadores lógicos" && e || são infixos. [relação binária unária] "operador" é IMHO o termo incorreto para eles, pois não são. Not é uma maneira semelhante à lógica bit a bit & e | que permitem sobrecarga (algo que não tenho certeza é uma boa escolha).

@PallHaraldsson Esses são fluxos de controle, não operadores no mesmo sentido que & , | , + , etc.

Vamos tentar ficar no tópico aqui se possível, por favor.

@tkelman Esse é um bom ponto. Eu suspeito que podemos tornar a sintaxe de tubulação futura compatível com versões anteriores. Por exemplo, se _ for reservado, então |> pode ter um significado especial quando seus argumentos contiverem _ e, caso contrário, fazer a mesma coisa que agora.

Há outro problema: para fazer |> funcionar para seu objeto, você define |> ou o "operador de chamada de função" (ou seja, adicionando métodos a ele)? Pode ser mais limpo se |> fosse uma sintaxe embutida para chamada de função, para garantir que f(x) e x |> f sejam sempre os mesmos.

O consenso aqui é muito claramente contra, então vou em frente e encerro o assunto. Eu aprecio a discussão, todos.

Eu sei que este assunto está encerrado. Só queria dizer "obrigado" por manter o operador.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

TotalVerb picture TotalVerb  ·  3Comentários

omus picture omus  ·  3Comentários

i-apellaniz picture i-apellaniz  ·  3Comentários

dpsanders picture dpsanders  ·  3Comentários

sbromberger picture sbromberger  ·  3Comentários