Julia: Operadores infixos personalizados

Criado em 17 jun. 2016  ·  65Comentários  ·  Fonte: JuliaLang/julia

Há uma discussão em https://groups.google.com/forum/#!topic/julia -dev/FmvQ3Fj0hHs sobre como criar uma sintaxe para operadores infixos personalizados.

...

Editado para adicionar nota: @johnmyleswhite apontou que o tópico de comentários abaixo é um convite ao bikeshedding. Por favor, evite novos comentários, a menos que você tenha algo realmente novo para adicionar. Existem várias propostas abaixo, marcadas por emoticons "hooray" (cone explodindo). Você pode usar esses ícones para pular a discussão e apenas ler as propostas, ou encontrar as diferentes propostas para poder votar "polegar para cima" ou "polegar para baixo".

Os votos positivos/negativos neste bug como um todo são sobre se você acha que Julia deveria ter algum idioma de infixo personalizado. Votos positivos/negativos para a ideia específica abaixo devem ir no primeiro comentário de @Glen-O. (O bug teve 3 votos negativos e 1 voto positivo antes de ser esclarecido.)

...

Proposta inicial (apenas interesse histórico):

A proposta que parece ter vencido é:

    a |>op<| b #evaluates (in the short term) and parses (in the long term) to `op(a,b)`

Para ter este trabalho, há apenas pequenas alterações necessárias:

  • Coloque a precedência de <| acima de |> , em vez de ser a mesma.
  • Faça o grupo <| da esquerda para a direita.
  • Faça a função <|(a,b...)=(i...)->a(i...,b...) . (como apontado no tópico de discussão, isso teria usos autônomos, bem como seu uso no idioma acima)

Opcional:

  • crie novas funções >|(a...,b)=(i...)->b(a...,i...) e |<(a,b...)=a(b...) com precedências e agrupamento apropriados.

    • Pipe first significa avaliação, e pipe last a mantém como uma função, enquanto > e < indicam qual é a função.

  • crie novas funções >>|(a...,b)=(i...)->b(i...,a...) e <<|(a,b...)=(i...)->a(b...,i...) com precedência e agrupamento apropriados.
  • criar sinônimos » , e(/ou) pipe para |> ; « , e(/ou) rcurry por <| ; e(/ou) lcurry para <<| ; com os sinônimos de caractere único funcionando como operadores infixos.
  • crie uma macro @infix na base que faça a primeira correção do analisador abaixo.

Longo prazo:

  • ensine o analisador a alterar a |>op<| b para op(a,b) , para que não haja sobrecarga extra envolvida ao executar o código e para que os operadores possam ser definidos na posição infixa. (Isso é semelhante a como o analisador atualmente trata o binário a:b e o ternário a:b:c maneira diferente. Para máxima personalização, ele deve fazer isso para sinônimos correspondentes, mas não para sinônimos não correspondentes, de modo que, por exemplo a |> b « c ainda seria tratado como dois operadores binários.)
  • ensine o analisador a entender vírgulas e/ou espaços para que as elipses nas definições acima funcionem conforme o esperado sem parênteses extras.

(refere-se a https://github.com/JuliaLang/julia/issues/6946)

parser speculative

Comentários muito úteis

Stefan não é mais velho do que eu.

Todos 65 comentários

Ecoando o tópico julia-dev, acho que seria útil citar o principal comentário de Stefan sobre esta proposta:

Apenas para definir as expectativas aqui, não acho que haverá muito em termos de "inovação sintática" antes do Julia 1.0. (A única exceção que consigo pensar é a nova sintaxe de chamada vetorizada f.(v) .) Embora ter alguma maneira de fazer funções arbitrárias se comportarem como operadores infixos possa ser bom, não é um problema urgente na linguagem.

Como alguém que participou de boa parte da história do desenvolvimento de Julia, acho que seria melhor concentrar a energia nas mudanças semânticas do que nas sintáticas. Há muitos problemas semânticos extremamente importantes a serem resolvidos antes que Julia chegue a 1.0.

Observe em particular que a implementação desse recurso não é simplesmente uma diferença única na qual apenas o autor precisa pensar: todos terão que pensar em como seu trabalho interage com esse recurso daqui para frente, para que a mudança realmente aumente o longo prazo carga de trabalho de cada pessoa que trabalha no analisador.

Eu acho que os comentários de johnmyleswhite são muito apropriados em relação às mudanças de analisador de "longo prazo" sugeridas. Mas os grupos de "pequenas mudanças" e "opcionais" são, até onde posso ver, bastante independentes e de baixo impacto.

Ou seja: as mudanças de parser necessárias para habilitar a versão mínima desta proposta envolvem apenas precedência e agrupamento para operadores binários normais, o tipo de mudanças que são mais ou menos rotineiras em outros casos. Um desenvolvedor de parser trabalhando em algo não relacionado não precisaria mais acompanhar isso do que precisaria acompanhar o significado de todos os numerosos operadores já existentes.

Pessoalmente, acho essa sintaxe bastante feia e difícil de digitar. Mas eu concordo que seria bom ter uma sintaxe de infixo mais geral.

Eu acho que a maneira correta de pensar sobre isso é como um problema apenas de sintaxe: o que você quer é usar op com sintaxe infixa, portanto, definir outras funções e operadores para obter isso é indireto. Em outras palavras, tudo deve ser feito no analisador.

Eu realmente consideraria recuperar | por isso e usar a |op| b . Indiscutivelmente a sintaxe infixa geral é mais importante do que bit a bit ou. (Já falamos sobre a recuperação de operadores bit a bit antes; eles parecem um desperdício de sintaxe.)

a f b está disponível fora da concatenação de matrizes e sintaxes de chamada de macro.

a f b pode funcionar, mas parece bem frágil. Imagine tentar explicar a alguém porque a^2 f b^2 f c^2 é legal, mas a f b c e a+2 f b+2 f c+2 não são. (Eu sei, esse último assume que a precedência é prec-times, mas não importa qual seja a precedência, esse tipo geral de coisa é uma preocupação).

Quanto a a |op| b : inicialmente eu favoreci uma proposta semelhante, a %op% b , como você pode ver no tópico dos grupos do google. Mas a coisa boa sobre os |> e <| propostos é que eles são individualmente úteis como operadores binários, e eles naturalmente se combinam para funcionar como desejado (dada a precedência e agrupamento corretos, isto é. ) Isso significa que você pode implementar isso no curto prazo usando mecanismos de analisador existentes e, assim, evitar criar dores de cabeça para desenvolvedores de analisadores no futuro, como eu disse em minha resposta a johnmyleswhite acima.

Então, embora eu goste a |op| b e certamente não me oponha a isso, acho que devemos procurar uma maneira de ter dois operadores diferentes para simplificar as alterações necessárias no analisador. Se estamos buscando a máxima tipabilidade e não nos opomos a | significar "pipe" em vez de "bitwise or", então que tal a |op\\ b ou a |op& b ?

"dores de cabeça para desenvolvedores de analisadores" é a menor preocupação possível.

"dores de cabeça para desenvolvedores de analisadores" é a menor preocupação possível.

Como desenvolvedor de analisadores, concordo inequivocamente com isso.

|> e <| são operadores infixos perfeitamente bons, mas não há nenhum benefício em implementar a sintaxe geral do operador usando dois outros operadores. E muito mais precisa ser dito sobre o quão verbosa e desagradável é essa sintaxe.

não há nenhum benefício em implementar a sintaxe geral do operador usando dois outros operadores.

Para ser claro, a visão de longo prazo aqui é que haveria binário f <| y , binário x |> f e ternário x |> f <| z , onde o primeiro é apenas uma função, mas o segundo dois são implementados como transformações no analisador.

A ideia de que isso poderia ser implementado usando duas funções comuns |> e <| é apenas uma ponte temporária para essa visão.

E muito mais precisa ser dito sobre o quão verbosa e desagradável é essa sintaxe.

Esse é um ponto justo. Que tal substituir |> e <| por | e & ? Eles fazem sentido tanto como um par quanto individualmente, embora possam ser um pouco chocantes para um pouco jogador de hóquei.

Roubar | e & para isso não seria uma boa alocação de ASCII, e suspeito que muitos prefeririam que os delimitadores fossem simétricos.

Se as pessoas quiserem um operador ternário x |> f <| y por outros motivos, tudo bem, mas acho que deve ser considerado separadamente. Não tenho certeza se o analisador deve transformar |> em um <| invertido. Outros operadores semelhantes como < não funcionam dessa maneira. Mas isso também é uma questão separada.

Roubando ambos | e & para isso não seria uma boa alocação de ASCII, e suspeito que muitos prefeririam que os delimitadores fossem simétricos.

OK.

Eu entendo que > e < são difíceis de digitar. Em termos de simetria e tipabilidade em um teclado padrão, acho que o mais fácil pode ser algo como &% e %& , mas isso é muito feio, R paralelo ou não. /| e |/ podem valer a pena considerar também.

...

Não tenho certeza se o analisador deve transformar |> em um <| invertido

Acho que você entendeu errado. a |> b deve analisar para b(a) . (A versão sem análise especial seria ((x,y)->y(x))(a,b) , que avalia a mesma coisa, mas com mais sobrecarga.)

a |> b deve analisar para b(a)

Ah, ok, entendi.

Acho que poderíamos discutir sobre quais personagens usar por anos. Eu confiaria em @StefanKarpinski (como a pessoa mais sênior nesta conversa até agora) para tomar uma decisão, e eu ficaria bem com isso. Mesmo que seja algo contra o qual argumentei (como a f b .)

Veja algumas opções para ver o que atrai:
a |>op<| b (deixando o atual |> inalterado)
a |{ op }| b (próximo e mesmo estado de deslocamento em muitos teclados comuns, não muito feios. Um pouco estranhos como autônomos.)
a \| op |\ b ou a /| op |/ b ou suas combinações
a $% op %$ b (relativamente tipável, inspirado em R. Mas meio feio.)
a |% op %| b
a |- op -| b
a |: op :| b
a | op \\ b
a | op ||| b
a op b

Stefan não é mais velho do que eu.

Parece que você acabou de se nomear, então, para os poderes do BDFL nesta questão! ;)

a @op@ b ?

Acho que meu voto é usar todos os 4 \| , |\ , /| e |/ . Para baixo para avaliação, para cima para curry; barra para a função. Assim:
a \| f (ou f |/ a ) -> f(a)
a /| f (ou f |\\ a ) -> (b...)->f(a,b...)
f |\ b (ou b //| f ) -> (a...)->f(a...,b)
e assim:
a \| f |\ b (ou a /| f |/ b ) -> f(a,b)
a \| f |\ b |\ c (ou a /| b /| f |/ c ) -> f(a,b,c)

Cada um dos 4 operadores principais, exceto talvez |/ , é útil por si só. A redundância certamente seria não-Pythonic, mas acho que a limpeza lógica é Julian. E na prática, você pode usar qualquer versão do idioma infixo que achar mais fácil de digitar; ambos são igualmente legíveis, uma vez que você aprende um, naturalmente entende os dois.

Obviamente, faria o mesmo sentido se você trocasse todas as barras, de modo que as setas para cima fossem para avaliação e para baixo para curry.

Ainda estou esperando notícias de On High (e peço desculpas pela minha falta de jeito novato em adivinhar o que isso significava). Mas se alguém mais alto do que esta bikeshed fizer uma decisão, para esta ou qualquer outra versão com pelo menos dois novos símbolos, eu ficaria feliz em escrever um patch de curto prazo (usando funções) e/ou adequado (usando transformações).

Tentamos evitar ter um BDFL na medida do possível :)

Só pensei em anotar algumas coisas rápidas.

Primeiro, o outro benefício (os "usos autônomos") da notação que está sendo proposta é que <| pode ser usado em outros contextos, de forma a melhorar a legibilidade. Por exemplo, se você tem uma matriz de strings, A , e deseja preencher todas elas à esquerda para 10, agora, você precisa escrever map(i->lpad(i,10),A) . Isso é relativamente difícil de ler. Com esta notação, torna-se map(lpad<|10,A) , que acho que você concordará que é significativamente mais limpo.

Segundo, a ideia por trás disso é manter a notação consistente. Já existe um operador |> , que existe para mudar a "correção" de uma chamada de função de prefixo para postfix. Isso apenas estende a notação.

Terceiro, a possibilidade de usar infixo direto como a f b tem um problema maior. a + b e a * b acabariam tendo a mesma precedência, pois + e * são nomes de funções, e seria inviável para o sistema têm precedência variável. Isso, ou teria que tratar os operadores infixos existentes de forma diferente, o que poderia causar confusão.

Por exemplo, se você tem uma matriz de strings, A , e deseja preencher todas elas à esquerda para 10, agora, você precisa escrever map(i->lpad(i,10),A) . Isso é relativamente difícil de ler. Com esta notação, torna-se map(lpad<|10,A) , que acho que você concordará que é significativamente mais limpo.

Eu enfaticamente não concordo. A sintaxe proposta é – perdoe-me – salada ASCII, beirando algumas das piores ofensas de Perl e APL, sem precedentes em outras linguagens para dar ao leitor casual uma pista do que está acontecendo. A sintaxe atual, embora com alguns caracteres a mais (cinco?), é bastante clara para quem sabe que i->expr é uma sintaxe lambda – que está em um grande e crescente conjunto de linguagens.

a + b e a * b acabariam tendo que ter a mesma precedência, já que + e * são nomes de funções, e seria inviável o sistema ter precedência variável. Isso, ou teria que tratar os operadores infixos existentes de forma diferente, o que poderia causar confusão.

Eu não acho que isso seja um problema real; podemos apenas dizer qual é a precedência do infixo a f b e manter todos os níveis de precedência existentes também. Isso funciona porque a precedência é determinada pelo nome da função; qualquer função chamada "+" terá precedência "+".

Sim, já fazemos isso para a sintaxe 1+2 in 1+2 e não tem sido um problema.

Eu não acho que isso seja um problema real; podemos apenas dizer qual é a precedência do infixo afb e manter todos os níveis de precedência existentes também. Isso funciona porque a precedência é determinada pelo nome da função; qualquer função chamada "+" terá precedência "+".

Eu não quis dizer que é difícil escrever o analisador para fazê-lo funcionar. Eu quis dizer que isso leva a problemas de consistência, daí eu dizer "ou teria que tratar os operadores infixos existentes de maneira diferente, o que poderia causar confusão". Entre outras coisas, considere que ¦ e não parecem tão diferentes em conceito, mas um é um operador infixo predefinido, enquanto o outro não é.

Eu enfaticamente não concordo. A sintaxe proposta é – perdoe-me – salada ASCII, beirando algumas das piores ofensas de Perl e APL, sem precedentes em outras linguagens para dar ao leitor casual uma pista do que está acontecendo. A sintaxe atual, embora com alguns caracteres a mais (cinco?), é bastante clara para quem sabe que i->expr é uma sintaxe lambda – que está em um grande e crescente conjunto de linguagens.

Talvez eu devesse ser mais claro no que estou dizendo. Estou dizendo que ser capaz de descrever a operação como "lpad por 10" é muito mais claro do que i->lpad(i,10) torna isso. E na minha opinião, lpad<|10 é o mais próximo que você pode chegar disso, de uma forma não específica de contexto.

Talvez ajude se eu descrever de onde venho. Sou um matemático e físico matemático, antes de mais nada, e a "sintaxe lambda", embora sensata do ponto de vista da programação, não é a mais clara para quem tem menos experiência em programação. Julia é, como eu entendo, principalmente destinada a ser uma linguagem de computação científica, daí a forte semelhança com o MATLAB.

Devo perguntar - como lpad<|10 é mais "salada ASCII" do que, digamos, x|>sin|>exp ? No entanto, a notação |> foi adicionada. Compare com, digamos, scripts bash, onde | é usado para passar o argumento da esquerda para o comando da direita - se você sabe que é chamado de "pipe", faz um pouco mais de sentido, mas se você não são habilidosos em programação, não vai fazer sentido. A esse respeito, |> realmente faz mais sentido, pois se parece vagamente com uma flecha. E então <| é uma extensão natural da notação.

Compare com algumas das outras sugestões, como %func% , que _tem_ um precedente em outra linguagem, mas que é completamente opaco para pessoas que não possuem amplo conhecimento de programação na linguagem.

Veja bem, olhei um pouco para trás em uma das discussões mais antigas e vejo que houve uma notação usada em outra linguagem que seria muito boa, em teoria. Haskell aparentemente usa a |> b c d para representar b(a,c,d) . Se os espaços após o nome de uma função permitissem especificar "parâmetros" dessa maneira, funcionaria bem - map(lpad 10,A) . O único problema surge com os operadores unários - map(+ 10,A) produziria um erro, por exemplo, pois interpretaria em "+10" em vez de i->+(i,10) .

Em a f b : os problemas de precedência podem não ser tão ruins quanto Glen-O sugeriu, mas a menos que as funções infixas definidas pelo usuário tenham a precedência mais baixa, elas existem. Digamos, por uma questão de argumento, damos a eles prec-times. Nesse caso,
a^2 f b^2 => f(a^2,b^2)
a+2 f b+2 => a+f(2,b)+2
a^2 f^2 b^2 => (f^2)(a^2,b^2)
a f+2 b => erro de sintaxe?

Isso tudo é uma consequência natural de como você escreveria o analisador, então não é particularmente uma dor de cabeça nesse sentido. Mas não é particularmente intuitivo para o usuário casual do idioma.

Sobre a utilidade de um idioma de curry
Concordo com Glen-O que (i)->lpad(i,10) é simplesmente pior do que lpad<|10 (ou, se assim o desejarmos, lpad |\ 10 , ou qualquer outra coisa). O i é um fardo cognitivo totalmente estranho e uma fonte potencial de erros; na verdade, eu juro que quando eu estava digitando isso agora, sem querer digitei (i)->lpad(x,10) inicialmente. Então, ter uma operação de curry infixo me parece uma boa ideia.
No entanto, se essa for a intenção, qualquer que seja o idioma infixo que escolhermos, podemos criar nossa própria operação de curry. Se for a f b , então algo como lpad rcurry 10 seria bom. O ponto é legibilidade, não pressionamentos de tecla. Então eu acho que este é apenas um argumento fraco para <| .

Em a |> b c d
Gosto muito desta proposta. Acho que poderíamos fazer com que |> aceitasse espaços em ambos os lados, então a b |> f c d => f(a,b,c,d) .

(Observação: se minha sugestão de a b |> f c d e de Glen-O de map(lpad 10,A) , isso cria um caso de canto: (a b) |> f c d => f((x)->a(x,b),c,d) . Mas eu acho que é tolerável.)

Isso ainda tem problemas semelhantes em termos de precedência de operador como a f b . Mas de alguma forma eu acho que eles são mais toleráveis ​​se você puder pelo menos falar sobre eles em termos de precedência do operador |> , em vez de ser a precedência do operador ternário de com .

Experimente lpad.(["foo", "bar"], 10) em 0,5. O |> existente não é exatamente amado por todos.

@tkelman : Eu vejo o problema, mas qual é o seu ponto? Você acha que devemos consertar o |> existente antes de adicionarmos usos extras para ele? Se sim, como?

Pessoalmente, acho que devemos nos livrar do |> existente.

Experimente lpad.(["foo", "bar"], 10) em 0,5. O |> existente não é exatamente amado por todos.

Acho que você perdeu o ponto. Sim, a notação func.() é boa e contorna o problema em algumas situações. Mas eu uso a função map como uma demonstração simples. Qualquer função que receba uma função como argumento seria beneficiada por esta configuração. Como exemplo, puramente para demonstrar meu ponto, você pode querer classificar alguns números com base em seu mínimo múltiplo comum com algum número de referência. O que parece mais limpo e fácil de ler: sort(A,by=i->lcm(i,10)) ou sort(A,by=lcm 10) ?

Eu gostaria de observar mais uma vez que qualquer maneira de definir operadores infixos permitirá criar um operador que faça o que Glen-O quer que <| faça, de modo que na pior das hipóteses ele possa escrever algo como sort(A,by=lcm |> currywith 10) . O objetivo desta página é discutir como fazer alguns a ... f ... b => f(a,b) . Eu entendo que se os |> existentes ou os <| propostos são operadores que valem a pena tem alguma relação com esse ponto, mas vamos tentar não nos desviar muito.

Pessoalmente, acho que a proposta a |> b c é a melhor até agora. Segue uma convenção existente de Haskell; está logicamente relacionado ao operador |> existente; é razoavelmente legível e razoavelmente fácil de digitar. O fato de eu sentir que se estende naturalmente a outros usos é secundário. Se você discordar, por favor, pelo menos mencione seus sentimentos sobre o idioma principal, não apenas os usos secundários propostos.

Eu quis dizer que isso leva a problemas de consistência, daí eu dizer "ou teria que tratar os operadores infixos existentes de maneira diferente, o que poderia causar confusão".

Concordo que é difícil decidir sobre a precedência de a f b . Por exemplo in claramente se beneficia da precedência de comparação, mas é bem provável que muitas funções usadas como infixo não desejem precedência de comparação. No entanto, não vejo nenhum problema de consistência. Operadores diferentes têm precedência diferente. Adicionar a f b não força nossa mão a dar a + e * a mesma precedência.

Observe que |> já tem precedência adjacente à comparação. Para qualquer outra precedência, francamente, acho que os parênteses estão bem.

Se você não concorda comigo, e se estivéssemos usando a |> f b , então poderia haver operadores semelhantes |+> , |*> e |^> , que funcionou da mesma forma que |> , mas teve a precedência de seu operador central. Acho um exagero, mas é uma possibilidade.

Outra possibilidade para resolver o problema de precedência é usar uma sintaxe para operadores infixos personalizados que incluam algum tipo de parênteses, por exemplo, (a f b) .

Discussões relacionadas: https://github.com/JuliaLang/julia/issues/554 , https://github.com/JuliaLang/julia/issues/5571 , https://github.com/JuliaLang/julia/pull/14476 , https://github.com/JuliaLang/julia/issues/11608 e https://github.com/JuliaLang/julia/issues/15612.

Devo perguntar - como lpad<|10 é mais "salada ASCII" do que, digamos, x|>sin|>exp? No entanto, a notação |> foi adicionada.

Imagino que @tkelman argumenta

devemos nos livrar do |> existente.

em parte porque _ambos_ lpad<|10 e x|>sin|>exp se aventuram no território de saladas ASCII :).

Acho que o (a f b) do @toivoh , com parênteses obrigatórios, é a melhor proposta até agora.

Relacionado a https://github.com/JuliaLang/julia/issues/11608 (e, portanto, também https://github.com/JuliaLang/julia/issues/4882 e https://github.com/JuliaLang/julia/pull /14653): Se (a f b) => f(a,b) , então faria sentido se (a <strong i="8">@m</strong> b) => (<strong i="10">@m</strong> a b) . Isso permitiria substituir a lógica de macro de caso especial existente para y ~ a*x+b com $# (y @~ a*x+b) 5$#$ normal (e, portanto, muito mais transparente).

Além disso, os "parênteses obrigatórios" podem ser a expressão idiomática preferida para definições concisas de infixos. Em vez de dizer (para usar um exemplo estúpido) a + b = string(a) * string(b) , você seria encorajado (por ferramentas lint ou por avisos do compilador) a dizer (a + b) = string(a) * string(b) . Eu percebo que isso não é realmente uma consequência direta de escolher a opção "parens obrigatórios" para o infix, mas é um idioma conveniente que nos permitiria avisar as pessoas que usam o infix no LHS por engano, mas dispensar as pessoas que o fazem no propósito.

Minha sensação é que, se você estiver aplicando uma função infix (em vez de prefix),
então é um operador, e deve parecer e agir como um operador.

E temos suporte para operadores infixos definidos usando unicode.
desde https://github.com/JuliaLang/julia/issues/552

Acho que seria bom ter isso estendido para que você possa adicionar as palavras-chave como na sugestão original.
Assim, poderíamos ter, por exemplo, 1 ⊕₂ 1 == 0
Ser capaz de ter nomes arbitrários para seu infixo parece um pouco excessivo.

deve parecer e agir como um operador.

Concordo que deve haver fortes convenções de nomenclatura para operadores infixos. Por exemplo: um caractere de unicode, ou termina em uma preposição. Mas essas devem ser convenções que se desenvolvem organicamente, não requisitos impostos pelo compilador. Certamente, não acho que o número 552 seja o fim da história; se houver dezenas de operadores codificados, deve haver uma maneira de adicionar mais programaticamente, mesmo que apenas para prototipagem de novos recursos.

...

Para mim, a proposta (a f b) (e (a <strong i="10">@m</strong> b) ) está muito acima do restante das propostas neste bug. Estou quase tentado a fazer um patch.

(a f b) => f(a,b)
(a f b c d) => f(a,b,c,d)
(a f) =>erro de sintaxe
(a+2 f+2 b+2) => (f+2)(a+2,b+2)
(t1=a t2=f t3=b) => (t1=f)((t2=a),(t3=b)) (o espaço tem a menor precedência possível, como em macros)

...

Seria inapropriado eu enviar um patch?

Não entendi os dois últimos casos:

(a+2 f+2 b+2)=>(f+2)(a+2,b+2)
(t1=a t2=f t3=b)=>(t1=f)((t2=a),(t3=b))

Acho a sintaxe (a f b c d) muito estranha. Como 1 + 2 + 3 pode ser escrito como +(1,2,3) , então f(a,b,c) não deveria ser escrito como (a f b f c) ?

No geral, pessoalmente, não estou convencido de que Julia deva oferecer suporte a operadores infixos personalizados além do que é permitido atualmente.

Eu posso ver dois problemas com (a f b c d) .

Primeiro, será difícil ler quando você tiver uma expressão mais complicada - uma das razões pelas quais os colchetes podem ser frustrantes é que muitas vezes pode ser difícil dizer, de relance, quais colchetes combinam com quais outros colchetes. É por isso que os operadores infixos e pós-fixados ( |> ) são desejáveis ​​em primeiro lugar. A pós-fixação, em particular, é apreciada porque permite uma leitura agradável e organizada da esquerda para a direita sem ter que lidar com colchetes todas as vezes.

Em segundo lugar, não deixa nenhuma maneira de fazer coisas como torná-lo elementar. Meu entendimento é que f.(a,b) será uma notação em 0,5 para fazer f operar elementwise em seus argumentos com broadcasting. Não haverá uma maneira legal de fazer a mesma coisa com a notação infixa, se for (a f b) . Na melhor das hipóteses, teria que ser (a .f b) , o que a meu ver perde a gentileza da simetria que .( oferece com .+ e .* .

Compare, por exemplo, o caso de querer usar o exemplo de Haskell. shashi em # 6946 fez o ponto que tem um equivalente aqui. Em Haskell, você escreveria circle 10 |> move 0 0 |> animate "scale" "ease" . Usando essa notação, isso se torna ((circle(10) move 0 0) animate "scale" "ease") , que não é mais claro que animate(move(circle(10),0,0),"scale","ease") . E se você quiser copiar seu círculo para vários lugares, usando a notação |> , talvez tenha circle 10 .|> copy [1 15 50] [3 14 25] . Na minha opinião, essa é a maneira mais simples de implementar a ideia - e, em seguida, os colchetes fazem seu papel normal de lidar com questões de ordem de operação.

E como eu apontei, a|>f b c tem o benefício de também ter uma extensão natural permitindo que a mesma notação tenha mais uso - f b c analisaria como "função f com parâmetros b e c set), e assim seria equivalente a i->f(i,b,c) . Isso permite que ele funcione não apenas para infixação, mas para outras situações em que você pode querer passar uma função (especialmente uma função embutida) com parâmetros (observando que o padrão é ter o objeto da função primeiro).

O |> também deixa claro qual é a função. Se você tivesse, digamos, (tissue wash fire dirty metal) , seria muito difícil, de relance, reconhecer wash como a função. Por outro lado, tissue|>wash fire dirty metal tem um grande indicador dizendo " wash é a função".

Algumas das objeções mais recentes me parecem dizer "mas você pode abusar desse recurso!" Minha resposta é: claro que você poderia. Você já poderia escrever código totalmente ilegível usando macros, se quisesse. O trabalho do analisador é habilitar usos legítimos; para acabar com os abusos, temos convenções/idiomas e em alguns casos delinquentes. Especificamente:

Não entendi os dois últimos casos:

Estes não pretendem de forma alguma ser um exemplo a seguir; eles estão apenas mostrando as consequências naturais das regras de precedência. Acho que os dois últimos exemplos se qualificariam como abuso da sintaxe, embora (a^2 ಠ_ಠ b^2) => ಠ_ಠ(a^2,b^2) seja bastante claro.

f(a,b,c) não deveria ser escrito como (afbfc)

Minha proposta de (a f b c d) foi, francamente, uma reflexão tardia. Acho que faz sentido, e eu poderia apresentar exemplos em que é útil, mas não quero suspender esta proposta sobre esse assunto se for controverso.

[Por exemplo:

  • f é um "método de objeto" de um objeto a, provavelmente complicado, usando b, c e d, provavelmente mais simples.
  • f é um método de "transmissão natural" como push! ]

Enquanto (a f b f c) faria sentido se f fosse como + , eu acho que a maioria dos operadores não são como + , então não deveria ser nosso modelo.

será difícil de ler quando você tiver uma expressão mais complicada

Novamente, minha resposta seria: "então não abuse".

Digamos que queremos alguma maneira de escrever uma expressão complicada como a / (b + f(c,d^e)) com f infixo. Na proposta do @toivoh , isso seria a / (b + (c f d^e)) . No uso semelhante ao Haskell, seria a / (b + (c |> f d^e)) ou no "melhor", se a precedência |> fosse alterada para corrigir este exemplo específico, a / (b + c |> f d^e) . Eu acho que o de @toivoh é facilmente tão bom aqui.

(tissue wash fire dirty metal)

Acho que a solução para isso são convenções de nomenclatura fortes para operadores infixos. Por exemplo, se houvesse uma convenção de que os operadores infixos deveriam ter um caractere de unicode, ou terminar em uma preposição ou sublinhado, então o acima seria algo como (tissue wash_ fire dirty metal) que é o mais claro que essa expressão poderia esperar ser .

...

elementarmente

Esta é uma preocupação válida. (a .f b) é uma má ideia, porque pode ser lido como ((a.f) b) . Minha primeira sugestão é (a ..f b) mas não me deixa muito feliz.

circle 10 |> move 0 0 |> animate "scale" "ease"

Eu usei jquery, então definitivamente vejo a vantagem do encadeamento de funções assim. Mas acho que não é o mesmo problema dos operadores infixos. Usando a proposta (a f b) , você poderia escrever o acima como:

circle 10 |> (move <| 0 0) |> (animate <| "scale" "ease")

... que não é tão concisa quanto a versão Haskell, mas ainda bem legível.

Talvez possa ser limitado a apenas três coisas dentro do () :
(a f (b,c))
.(a f (b,c)) usando o operador .(

Por fim, uma resposta ao ponto geral:

No geral, pessoalmente, não estou convencido de que Julia deva oferecer suporte a operadores infixos personalizados além do que é permitido atualmente.

Obviamente, todos temos direito às nossas opiniões. (Não estou claro se o polegar para cima se referia a essa parte do comentário, mas, se sim, isso triplica.)

Mas meus contra-argumentos são:

  • Julia já tem dezenas de operadores infixos, muitos deles extremamente nicho. É inevitável que mais sejam propostos. Quando alguém diz "como você pode ter mas não § ?", eu prefiro responder "faça você mesmo" e não "espere até que a próxima versão seja amplamente adotada".
  • Algo como (a § b) é eminentemente legível, e a sintaxe é leve o suficiente para aprender com um ou dois exemplos.
  • Não sou a primeira pessoa a levantar esta questão e não serei a última. Eu entendo que os designers de linguagem devem ser muito, muito céticos em relação a (des)recursos rastejantes, porque uma vez que você adiciona um recurso feio, é basicamente impossível consertar mais tarde. Mas como eu disse acima, acho que (a f b) é limpo o suficiente para você não se arrepender.

Eu realmente não tenho certeza sobre a clareza de (a f b)

Aqui está um possível caso de uso:
select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,'indianapolis')) orderby :emp_id));

Este é certamente o uso viável de funções infixas.
A função select é a função de identidade ou envia a consulta criada para o banco de dados.

Este código é claro?
Eu simplesmente não sei.

.(a f b)

Sim, isso faz sentido. Mas não é muito legível.

(a @. f b) é mais legível? Porque a macro @. para habilitar isso seria uma linha simples.

[[[Pensando nisso, se permitíssemos macros infixas sem exigir parênteses, @Glen-O poderia usá-las para fazer o que ele quiser: circle(10) @> move 0 0 @> animate "scale" "ease" => @> (@> circle(10) move 0 0) animate "scale" "ease" =macro> animate(move(circle(10),0,0),"scale","ease") . Acho que essa solução é mais feia que (a f b) , mas pelo menos resolveria esse bug geral aos meus olhos.]]]

...

select((((:emp_id, :last_name) from employee_tbl) where (:city, = ,'indianapolis')) orderby :emp_id);

Eu definitivamente preferiria usar uma macro para "onde" para que a expressão condicional não precisasse ser citada de maneira estranha. Assim:

select((((:emp_id, :last_name) from employee_tbl) <strong i="22">@where</strong> city == 'indianapolis') orderby :emp_id);

Os parênteses são levemente irritantes, mas, por outro lado, não vejo uma maneira razoável de o analisador lidar com esse tipo de expressão sem eles.

select((((:emp_id, :last_name) from employee_tbl) <strong i="6">@where</strong> city == 'indianapolis') orderby :emp_id);

Os parênteses são levemente irritantes, mas, por outro lado, não vejo uma maneira razoável de o analisador lidar com esse tipo de expressão sem eles.

Pensando bem, a precedência nessa expressão é da direita para a esquerda. Então, usando macros infixas, poderia ser:

select((:emp_id, :last_name) <strong i="11">@from</strong> employee_tbl <strong i="12">@where</strong> city == 'NYC' <strong i="13">@orderby</strong> :emp_id)

ou ainda:

<strong i="17">@select</strong> (:emp_id, :last_name) <strong i="18">@from</strong> employee_tbl <strong i="19">@where</strong> city == 'NYC' <strong i="20">@orderby</strong> :emp_id

Então, embora eu ainda goste de (a f b) , estou começando a ver que macros infixas também são uma boa resposta.

Segue a proposta completa através de exemplos, seguida das vantagens e desvantagens:

principais usos:

  • a <strong i="28">@m</strong> b => <strong i="30">@m</strong> a b
  • a <strong i="33">@m</strong> b c => <strong i="35">@m</strong> a b c
  • a <strong i="38">@m</strong> b <strong i="39">@m2</strong> c => <strong i="41">@m2</strong> (<strong i="42">@m</strong> a b) c
  • <strong i="45">@defineinfix</strong> f; a <strong i="46">@f</strong> b => macro f(a,b...) :(f($a,$b...)) end; <strong i="48">@f</strong> a b => f(a,b)

Casos de canto: (não pretende ser um bom código, apenas para mostrar como o analisador funcionaria)

  • t1=a <strong i="54">@m</strong> t2=b t3=c => <strong i="56">@m</strong> t1=a t2=b t3=c (embora este não seja um bom estilo de programação)
  • t1 + a <strong i="59">@m</strong> t2 + b => <strong i="61">@m</strong> t1+a t2+b (embora este não seja um bom estilo de programação)
  • a b <strong i="64">@m</strong> c => erro de sintaxe (??)
  • a <strong i="67">@m</strong> b [c,d] => por favor , não, mas <strong i="70">@m</strong> a b[c,d] (ETA: Não, com o patch isso sai como <strong i="72">@m</strong> a b ([c,d]) que provavelmente é melhor.)
  • a <strong i="75">@m</strong> b ([c,d]) => <strong i="77">@m</strong> a b ([c,d])
  • [a <strong i="80">@m</strong> b] => estilo ruim, por favor use parênteses para esclarecer, mas [a (<strong i="83">@m</strong> b)] (??)
  • a @> f b => @> a f b => f(a,b)
  • <strong i="90">@outermacro</strong> a b <strong i="91">@m</strong> c d => <strong i="93">@outermacro</strong> a (<strong i="94">@m</strong> b c d)

Vantagens:

  • defina macros infix, obtenha funções infix gratuitamente (com sobrecarga única de avaliação de macro. Isso não é tão baixo quanto a mágica do analisador, mas muito melhor do que ter chamadas de função extras a cada avaliação)
  • pode levar a DSLs poderosas, como visto no exemplo semelhante a SQL acima
  • Elimina a necessidade de um operador |> separado, já que é uma macro de uma linha. Da mesma forma para <| e o resto das propostas de @Glen-O.
  • explícito, então muito baixo risco de ser usado por acidente, ao contrário de (a f b)
  • Como mostrado, a macro @defineinfix pode permitir o uso abreviado para funções e não macros.

(Menor) Desvantagens:

  • precedência e agrupamento parecem funcionar bem na maioria dos casos com RtoL, mas haveria exceções que exigiriam parênteses.
  • Eu acho que a @> f b ou mesmo a <strong i="112">@f</strong> b não é tão legível quanto (a f b) (embora eles também não sejam tão horríveis.)

Dado o quão ativo esse tópico se tornou, vou lembrar às pessoas da minha preocupação original com este tópico: problemas sobre sintaxe geralmente geram uma enorme quantidade de atividade, mas essa quantidade de atividade geralmente é desproporcional ao valor de longo prazo da mudança em debate. Em grande parte, isso ocorre porque os tópicos sobre sintaxe acabam se aproximando de argumentos puros sobre gostos.

que a quantidade de atividade é geralmente desproporcional

Eu sinto Muito. Eu provavelmente sou o mais culpado de entrar em vai-e-vem.

Por outro lado, acho que este tópico claramente fez um progresso "utilizável". Qualquer uma das sugestões mais recentes (a f b) ou [ a @> f b , com a <strong i="10">@f</strong> b definível como um atalho] é claramente superior, na minha opinião, às sugestões anteriores, como a %f% b ou a |> f <| b .

Ainda assim, acho que mais comentários de vai-e-vem provavelmente não farão nenhum progresso, e eu encorajo as pessoas a usarem o polegar para cima ou para baixo de agora em diante, a menos que tenham algo realmente novo para sugerir (que é, não apenas uma mudança ortográfica para uma proposta existente). Eu adicionei emoticons "hooray" (cone explodindo) às "propostas votáveis". Se você acredita que não devemos ter uma sintaxe especializada para funções arbitrárias na posição infixa, então dê um downvote no bug como um todo.

...

ETA: Acho que essa discussão já está madura o suficiente para obter uma tag decision .

Para referência, (e eu esperava que alguém apontasse isso).
Se você deseja incorporar uma sintaxe semelhante a SQL, a ferramenta certa para o trabalho é Nonstandard String Literals, eu acho.
Como todas as macros, elas têm acesso a todas as variáveis ​​no escopo quando chamadas,
e eles permitem que você especifique sua própria DSL, com sua própria escolha de prioridade, e eles são executados em tempo de compilação.

select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,"indianapolis")) orderby :emp_id));

é melhor escrito

sql"SELECT emp_id, last_name FROM employee_tbl WHERE city == 'indianapolis' ORDER BY emp_id"

Os literais de string não padronizados são uma sintaxe muito poderosa.
Não consigo encontrar bons exemplos deles sendo usados ​​para incorporar uma DSL.
Mas eles podem fazê-lo.

E neste caso acho que o resultado é muito mais limpo do que qualquer operação infixa que possa ser definida.
Embora tenha a sobrecarga de ter que escrever seu próprio microparser/tokenizer.


Eu realmente não vejo a necessidade de uma tag de decisão.
Isso não tem implementação como PR, nem qualquer protótipo utilizável.
que permite às pessoas testá-lo.
Compare com https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539 com seus 8 protótipos utilizáveis

Meus sentimentos em relação a isso sobem e descem toda vez que leio o tópico. Acho que não vou saber de verdade até experimentar. E agora eu nem sei para que eu iria usá-lo. (Ao contrário de algumas das definições para |> e <| que usei em F#)

Sintaxe semelhante a SQL, a ferramenta certa para o trabalho é o Nonstandard String Literals

Quer o SQL seja ou não melhor feito com NSLs, acho que há um nível de DSL que é complexo o suficiente para que as macros inline sejam muito úteis, mas não tão complexas que valha a pena escrever seu próprio microparser/tokenizer.

agora eu nem sei para que eu iria usá-lo. (Ao contrário de algumas das definições para |> e <| que usei em F#)

A proposta de macro inline permitiria que as pessoas, entre outras coisas, lançassem suas próprias macros |> -like ou <| -like, para que você pudesse usá-las para qualquer coisa que tenha feito em F#.

(Eu não quero entrar em discussões de vai e vem, mas eu estava respondendo de qualquer maneira por causa do abaixo, e eu acho que a proposta de macro inline mata vários pássaros com uma pedra relativamente lisa.)

Eu realmente não vejo a necessidade de uma tag de decisão.

Perguntei anteriormente se era apropriado criar um patch do analisador e ninguém respondeu. A única palavra sobre isso até agora é:

Eu não acho que haverá muito em termos de "inovação sintática" antes de Julia 1.0.

O que parece argumentar contra fazer um patch agora, pois pode apenas ficar parado e apodrecer. No entanto, agora você está dizendo que não vale a pena tomar uma decisão sobre isso (incluindo a decisão de não decidir agora?) a menos que tenhamos uma "implementação como um protótipo utilizável de relações públicas".

O que isso significa? (O que é um PR?) Uma macro que usasse o caractere '@' em vez do token @ faria o trabalho, de modo que <strong i="22">@testinline</strong> a '@'f b => @f(a, b) ? Ou devo enviar um patch para julia-parser.scm? (Na verdade, comecei a tentar escrever tal patch, e parece que deveria ser simples, mas meu Scheme está muito enferrujado.) Preciso criar casos de teste?

No momento, existem 13 participantes neste bug. Há um total de 5 pessoas que votaram em uma ou mais das propostas e/ou votaram negativamente no próprio bug, e apenas uma delas (eu) o fez depois que a proposta de macro inline estava na mesa. Isso não me deixa confiante de que é hora de prototipagem ainda. Quando o número de pessoas que votaram desde a última proposta séria for mais da metade do número de participantes, espero que algum tipo de consenso aproximado esteja se tornando claro, e então será hora de prototipar, testar e decidir (ou, como pode ser o caso, desistindo da ideia).

Por "implementação como um PR [ou] protótipo utilizável".
Quero dizer algo que pode ser jogado com.
Assim, pode-se ver como se sente na prática.

Um PR é um pull request, então um patch é o termo que você está usando.

Se você fez um PR, ele pode ser baixado e testado.
Mais simplesmente, se você o implementou com macros
ou literais de string não padrão,
poderia ser testado sem ter que construir julia.

Como se não fosse minha decisão, mas duvido que serei capaz de formar minha própria opinião sem algo com que eu possa brincar.

Também +1 para não ir e voltar de queda de bicicleta.

...ou talvez um pacote Infix.jl com macros e literais de string não padrão.

Definitivamente, chegamos ao ponto "código de trabalho ou GTFO" nesta conversa.

OK, aqui está o código de trabalho: https://github.com/jamesonquinn/JuliaParser.jl

ETA: Devo fazer referência a um commit específico ou o link acima para o mestre mais recente está OK?

...

(Isso não tem nenhuma das macros de conveniência que eu esperaria que você desejasse, como os equivalentes para |> , <| , ~ e o @defineinfix do meu exemplo acima. Nem remove _deprecate_ a lógica de caso especial agora inútil para ~ ou o operador |> . São apenas as alterações do analisador para fazê-lo funcionar. funcionalidade básica testada, mas nem todos os casos de canto.

...

Eu acho que o hack feio atual com ~ mostra que há um caso de uso claro para esse tipo de coisa. Usando este patch, você diria @~ quando precisasse de comportamento de macro; muito mais limpo, sem nenhum caso especial. Ou alguém acredita seriamente que ~ é totalmente único e ninguém nunca mais vai querer fazer isso de novo?

Observe que o patch (ainda não é um PR porque tem como alvo o analisador bootstrap nativo, mas por enquanto o esquema deve vir primeiro em termos de PRs) geralmente é mais útil do que o nome do problema aqui. O nome do problema é "operadores infixos personalizados"; o patch fornece macros infixas, com operadores infixos apenas como um efeito colateral disso.

O patch como está não é uma mudança importante, mas espero que, se isso se tornasse o plano, o próximo passo seria descontinuar os atuais ~ e |> , o que acabaria levando a quebra de mudanças.

...

Alguns testes simples adicionados.

11608 foi fechado com um consenso bastante claro de que muitos de nós não querem macros infixas e o único caso atual de análise ~ foi um erro (feito no início para compatibilidade com R e nenhuma outra razão especialmente boa). Pretendemos descontinuar e eventualmente nos livrar dele, mas ainda não o fizemos (junto com o trabalho de modificar a API para a interface de fórmula nos pacotes JuliaStats) ainda.

As macros agora são tecnicamente genéricas, mas seus argumentos de entrada são sempre Expr , Symbol ou literais. Portanto, eles não são realmente extensíveis a novos tipos definidos em pacotes como as funções (infixas ou não). Possíveis casos de uso para macros infixas são mais bem servidos por DSLs de macros com prefixo anotado ou literais de string.

(Desculpe, postei prematuramente; corrigido agora.)

Em #11608, vejo vários argumentos negativos:

===

Em que se transformaria o seguinte?
...
y = 0.0 @in@ x == 1.0 ? 1 @in@ 2 : 3 @in@ 4

Isso foi tratado no tópico:

Casos como esse são o motivo pelo qual eu sempre uso parênteses...

e

mesmo precedente... aplicam-se sem serem macros: 0.0 in 1 == 1.0 ? 2 in 2 : 3 in 4

===

mais funcionalidades para Julia que as pessoas precisam implementar, manter, testar, aprender a usar, etc.

que é (parcialmente) respondido (e apoiado) aqui por:

"dores de cabeça para desenvolvedores de analisadores" é a menor preocupação possível.

===

não há como 2 pacotes simultaneamente terem definições para o mesmo macro-operador que possam ser usados ​​juntos sem ambiguidade em uma única base de código de usuário?

Este é um ponto interessante. Obviamente, se a macro apenas chamar uma função, teremos todo o poder de despacho da função. Mas se for uma macro verdadeira, como ~ , então é mais complicado. Sim, você pode imaginar soluções hackish, como tentar chamá-lo como uma função e capturar quaisquer erros para usá-lo como uma macro ... mas esse tipo de feiúra não deve ser incentivado.

Ainda assim, isso é um problema para qualquer macro. Se dois pacotes exportam uma macro, você simplesmente não pode ter ambos com "usando".

É provável que isso seja mais um problema com macros infixas? Bem, depende para que as pessoas acabam usando-os para:

  • Apenas uma maneira de ter funções infixas definidas pelo usuário. Nesse caso, eles não são piores do que qualquer outra função; envio funciona bem.
  • Como forma de usar outros estilos de programação, usando operadores como |> e <| que @Glen-O discute acima. Nesse caso, acho que rapidamente se desenvolverão convenções comuns sobre o que macro significa o quê, com pouca chance de colisão.
  • Como uma forma de fazer DSLs para fins especiais, como o exemplo SQL acima. Eu acho que eles serão usados ​​em contextos específicos e a chance de colisão não é tão ruim.
  • Para coisas como ~ do R. A princípio, isso parece o mais problemático; em R, ~ é usado para várias coisas diferentes. No entanto, acho que mesmo lá, é gerenciável, com algo como:

macro ~(a,b) :(~(:$a, quote($b))) end

Então, a função ~ poderia despachar com base no tipo do LHS, mas o RHS sempre seria um Expr. Esse tipo de coisa permitiria que os principais usos que ele tem em R (regressão e gráficos) coexistissem, ou seja, despachar corretamente apesar de vir de pacotes diferentes.

(nota: o acima foi editado. Inicialmente, eu pensei que uma expressão R como a ~ b + c usava a ligação de b e c por meio da avaliação preguiçosa do R. Mas isso não acontece t; b e c são os nomes das colunas em um quadro de dados passados ​​explicitamente, não nomes de variáveis ​​no escopo local que são assim passados ​​implicitamente.)

===

A única maneira de avançar aqui seria desenvolver uma implementação real.

O que eu fiz.

===

As macros agora são tecnicamente genéricas, mas seus argumentos de entrada são sempre Expr, Symbol ou literais. Portanto, eles não são realmente extensíveis a novos tipos definidos em pacotes como as funções (infixas ou não).

Isso se relaciona com o ponto acima. Na medida em que uma macro infixa chama uma função específica, essa função ainda é extensível por despacho da maneira normal. Na medida em que não chama uma função específica, está fazendo algo estrutural/sintático (como o que |> faz agora) que não deve ser estendido ou redefinido. Observe que mesmo que ele chame uma função, o fato de ser uma macro ainda pode ser útil; por exemplo, ele pode citar alguns de seus argumentos, ou processá-los em retornos de chamada, ou até mesmo interagir simultaneamente com o nome e a ligação de uma variável, de uma forma que uma chamada de função direta não pode.

===

Possíveis casos de uso para macros infixas são mais bem servidos por DSLs de macros com prefixo anotado ou literais de string.

Como foi apontado no tópico referenciado:

[Infix é] mais fácil de analisar (para falantes de inglês e da maioria dos ocidentais), porque nosso idioma funciona dessa maneira. (A mesma coisa geralmente vale para os operadores.)

Por exemplo, o que é mais legível (e gravável):

select((:emp_id, :last_name) <strong i="8">@from</strong> employee_tbl <strong i="9">@where</strong> city == 'NYC' <strong i="10">@orderby</strong> :emp_id)

ou

send(orderby((<strong i="14">@where</strong> selectfrom((:emp_id, :last_name), employee_tbl) city == 'NYC'), :emp_id))

?

===

Finalmente:

11608 foi fechado com um consenso bastante claro

Parece bastante dividido para mim, com "quem vai fazer o trabalho" dando o voto decisivo. O que agora é pelo menos parcialmente discutível; Já fiz o trabalho no JuliaParser e estaria disposto a fazê-lo no Scheme se as pessoas gostarem dessa ideia.

Este é o meu último post neste tópico, a menos que haja uma reação positiva ao meu juliaparser hackeado. Não é minha intenção impor minha vontade; apenas para apresentar meu ponto de vista.

Estou argumentando a favor de macros infixas ( a <strong i="6">@m</strong> b => <strong i="8">@m</strong> a b ). Isso não significa que eu não esteja ciente dos argumentos contra. Aqui está como eu resumiria o melhor argumento contra:

Os recursos de idioma começam em -100. O que as macros infixas oferecem que poderia superar isso? Por sua própria natureza, não há nada que você possa realizar com macros de prefixo que não possa ser realizado com macros de prefixo.

Minha resposta é: Julia é antes de tudo uma linguagem para programadores STEM. Matemáticos, engenheiros, estatísticos, físicos, biólogos, pessoas de aprendizado de máquina, químicos, econometristas... E uma coisa que eu acho que a maioria dessas pessoas percebe é a utilidade de uma boa notação. Para dar um exemplo com o qual estou familiarizado em estatística: adicionar variáveis ​​aleatórias independentes é equivalente a convoluir PDFs, ou mesmo a convoluir derivadas de CDFs, mas muitas vezes expressar algo usando o primeiro pode ser uma ordem de magnitude mais concisa e compreensível do que o último .

Infixo versus prefixo versus pós-fixo é, até certo ponto, uma questão de gosto. Mas também existem razões objetivas para preferir o infixo em muitos casos. Enquanto prefixo e pós-fixo levam a precipitados indigestos de operadores back-to-back como aqueles que fazem os programadores Forth soarem como políticos alemães, ou aqueles que fazem os programadores Lisp soarem como uma caricatura chomskiana, o infixo coloca os operadores no que é muitas vezes o mais cognitivo lugar natural, o mais próximo possível de todos os seus operandos. Há uma razão pela qual ninguém escreve trabalhos de matemática em Forth, e por que até mesmo matemáticos alemães usam operadores infixos ao escrever equações.

Sim, macros infixas podem ser usadas para escrever código ofuscado. Mas as macros de prefixo existentes são igualmente propensas a abusos. Se não forem abusadas, as macros infixas podem levar a um código muito mais claro .

  • (a+b <strong i="18">@choose</strong> b) vence binomial(a+b,b) ;
  • score ~ age + treatment vence linearDependency(:score, :(age + treatment)) ;
  • domSelect("#logo") @| css "color" "red" @| fadeIn "slow" <strong i="25">@thenApply</strong> addClass "dummy" supera addOneTimeEventListener(fadeIn(css(domSelect("#logo"),"color","red"),"slow"),"done",(obj,evt)->addClass(obj,"dummy")) .

Percebo que estes são apenas exemplos de brinquedos, mas acho que o princípio é válido.

O acima poderia ser feito com literais de string não padrão? Bem, o segundo e o terceiro exemplos funcionariam como NSLs. Mas o problema com os NSLs é que eles oferecem muita liberdade: a menos que você esteja familiarizado com a gramática específica, não há como ter certeza de quais são os tokens de um NSL, muito menos sua ordem de operações. Com macros infixas, você tem liberdade suficiente para fazer todos os exemplos acima, mas não tanto que não fique claro ao ler o código "bom" quais são os tokens e para onde vão os parênteses implícitos.

O que precisa de certas coisas para ser movido de incógnitas desconhecidas para incógnitas conhecidas. E, infelizmente, não há um mecanismo para fazer isso. Seus argumentos precisam de uma estrutura que não existe.

Agora que <| é associativo à direita (#24153), a proposta inicial a |>op<| b funciona?

Eu não sei quantos operadores infixos em potencial isso afeta, mas eu realmente gostaria de usar <~ . O analisador não cooperará - mesmo que eu espalhe as coisas com cuidado, ele quer que a <~ b signifique a < (~b) .

<- tem um problema semelhante.

Desculpe se isso já está coberto por este ou outro problema, mas não consegui encontrá-lo.

Poderíamos exigir espaços em a < ~b ; adicionamos regras como essa antes. Então poderíamos adicionar <- e <~ como operadores infixos.

Obrigado @JeffBezanson , isso seria ótimo! Seria este um caso especial ou uma regra mais geral? Tenho certeza de que há alguns detalhes sobre qual deve ser a regra para permitir mais operadores infixos, fornecer código claro e previsível e quebrar o mínimo possível de código existente. De qualquer forma, agradeço a ajuda e a rápida resposta. Feliz Ano Novo!

Caso a <~ b seja diferente de a < ~b gostaria de ver a =+ 1 como erro (ou aviso pelo menos)

Eu sei que essa é uma discussão bastante antiga, e a pergunta feita foi feita há algum tempo, mas achei que valia a pena responder:

Agora que <| é associativo à direita (#24153), a proposta inicial a |>op<| b funciona?

Não, infelizmente, |> ainda tem precedência. A atualização feita faz com que, se você definir <|(a,b)=a(b) , então você possa fazer a<|b<|c com sucesso para obter a(b(c)) ... mas este é um conceito diferente.

Congelado durante 2 anos, um comentário e um commit 2 e 5 dias atrás!

Consulte Operadores binários personalizáveis ​​do documento f45b6be

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

Questões relacionadas

tkoolen picture tkoolen  ·  3Comentários

wilburtownsend picture wilburtownsend  ·  3Comentários

yurivish picture yurivish  ·  3Comentários

TotalVerb picture TotalVerb  ·  3Comentários

sbromberger picture sbromberger  ·  3Comentários