Julia: Operadores infijos personalizados

Creado en 17 jun. 2016  ·  65Comentarios  ·  Fuente: JuliaLang/julia

Hay una discusión en https://groups.google.com/forum/#!topic/julia -dev/FmvQ3Fj0hHs sobre la creación de una sintaxis para operadores de infijos personalizados.

...

Editado para agregar una nota: @johnmyleswhite ha señalado que el hilo de comentarios a continuación es una invitación a la eliminación de bicicletas. Absténgase de hacer nuevos comentarios a menos que tenga algo realmente nuevo que agregar. Hay varias propuestas a continuación, marcadas con emoticonos de "hurra" (cono explosivo). Puede usar esos íconos para omitir la discusión y simplemente leer las propuestas, o para encontrar las diferentes propuestas y poder votar "aprobado" o "rechazado".

Los votos a favor o en contra de este error en su conjunto se refieren a si crees que Julia debería tener algún modismo de infijo personalizado. Los votos positivos/negativos para la idea específica a continuación deben ir en el primer comentario de @Glen-O. (El error tuvo 3 votos negativos y 1 voto positivo antes de que se aclarara).

...

Propuesta inicial (solo interés histórico):

La propuesta que parece haber triunfado es:

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

Para tener este trabajo, solo hay cambios menores necesarios:

  • Ponga la precedencia de <| por encima de la de |> , en lugar de ser la misma.
  • Haga un grupo <| de izquierda a derecha.
  • Haz la función <|(a,b...)=(i...)->a(i...,b...) . (como se señaló en el hilo de discusión, esto tendría usos independientes, así como su uso en el idioma anterior)

Opcional:

  • cree nuevas funciones >|(a...,b)=(i...)->b(a...,i...) y |<(a,b...)=a(b...) con las precedencias y agrupaciones apropiadas.

    • Pipe first significa evaluación, y pipe last lo mantiene como una función, mientras que > y < indican cuál es la función.

  • cree nuevas funciones >>|(a...,b)=(i...)->b(i...,a...) y <<|(a,b...)=(i...)->a(b...,i...) con la precedencia y agrupación adecuadas.
  • crear sinónimos » , , y(o) pipe para |> ; « , , y(o) rcurry por <| ; y(o) lcurry por <<| ; con los sinónimos de un solo carácter trabajando como operadores infijos.
  • cree una macro @infix en la base que hace la primera corrección del analizador a continuación.

A largo plazo:

  • enséñele al analizador a cambiar a |>op<| b a op(a,b) , para que no haya gastos adicionales involucrados al ejecutar el código, y para que los operadores puedan definirse en posición infija. (Esto es similar a cómo el analizador actualmente trata el binario a:b y el ternario a:b:c manera diferente. Para una personalización máxima, debería hacer esto para los sinónimos coincidentes, pero no para los sinónimos no coincidentes, de modo que, por ejemplo, a |> b « c todavía se trataría como dos operadores binarios).
  • enséñele al analizador a comprender las comas y/o los espacios para que los puntos suspensivos en las definiciones anteriores funcionen como se esperaba sin paréntesis adicionales.

(se relaciona con https://github.com/JuliaLang/julia/issues/6946)

parser speculative

Comentario más útil

Stefan no es mayor que yo.

Todos 65 comentarios

Haciendo eco del hilo julia-dev, creo que sería útil citar el comentario principal de Stefan sobre esta propuesta:

Solo para establecer expectativas aquí, no creo que haya mucho en el camino de la "innovación sintáctica" antes de Julia 1.0. (La única excepción que se me ocurre es la nueva sintaxis de llamada vectorizada f.(v) ). Si bien tener alguna forma de hacer que las funciones arbitrarias se comporten como operadores infijos puede ser bueno, simplemente no es un problema apremiante en el lenguaje.

Como alguien que ha participado en buena parte de la historia del desarrollo de Julia, creo que sería mejor centrar la energía en los cambios semánticos que en los sintácticos. Quedan muchos problemas semánticos extremadamente importantes por resolver antes de que Julia llegue a 1.0.

Tenga en cuenta en particular que la implementación de esta función no es simplemente una diferencia única en la que solo el autor debe pensar: todos tendrán que pensar en cómo su trabajo interactúa con esta función en el futuro, por lo que el cambio en realidad aumenta el largo plazo. carga de trabajo de cada persona que trabaja en el analizador.

Creo que los comentarios de johnmyleswhite son muy apropiados con respecto a los cambios sugeridos en el analizador "a largo plazo". Pero los grupos de "cambios menores" y "opcionales" son, por lo que puedo ver, bastante autónomos y de bajo impacto.

Es decir: los cambios del analizador necesarios para habilitar la versión mínima de esta propuesta implican solo precedencia y agrupación para operadores binarios normales, el tipo de cambios que son más o menos rutinarios en otros casos. Un desarrollador de analizadores que trabaje en algo no relacionado no necesitaría realizar un seguimiento de esto más de lo que necesita realizar un seguimiento del significado de todos los numerosos operadores ya existentes.

Personalmente, encuentro esta sintaxis bastante fea y difícil de escribir. Pero estoy de acuerdo en que sería bueno tener una sintaxis de infijos más general.

Creo que la forma correcta de pensar en esto es como un problema de sintaxis únicamente: lo que quiere es usar op con sintaxis infija, por lo que definir otras funciones y operadores para obtener eso es indirecto. En otras palabras, todo debe hacerse en el analizador.

De hecho, consideraría reclamar | para esto y usar a |op| b . Podría decirse que la sintaxis de infijo general es más importante que bit a bit o. (Ya hemos hablado antes sobre la recuperación de operadores bit a bit; tal como están, parecen un desperdicio de sintaxis).

a f b está disponible fuera de la concatenación de matrices y las sintaxis de llamadas a macros.

a f b podría funcionar, pero parece bastante frágil. Imagínese tratar de explicarle a alguien por qué a^2 f b^2 f c^2 es legal pero a f b c y a+2 f b+2 f c+2 no lo son. (Lo sé, el último asume que la precedencia es prec-times, pero no importa cuál sea la precedencia, este tipo general de cosas es una preocupación).

En cuanto a a |op| b : inicialmente estaba a favor de una propuesta similar, a %op% b , como se puede ver en el hilo de los grupos de Google. Pero lo bueno de los |> y <| propuestos es que cada uno de ellos es útil individualmente como operador binario, y naturalmente se combinan para funcionar como se desee (es decir, dada la precedencia y la agrupación correctas). ) Esto significa que puede implementar esto a corto plazo utilizando los mecanismos de análisis existentes y, por lo tanto, evitar crear dolores de cabeza para los desarrolladores de analizadores en el futuro, como dije en mi respuesta a johnmyleswhite arriba.

Entonces, aunque me gusta a |op| b y ciertamente no me opondría, creo que deberíamos buscar una forma de tener dos operadores diferentes para simplificar los cambios necesarios en el analizador. Si buscamos la máxima tipificación y no nos oponemos a que | signifique "tubería" en lugar de "bit a bit o", entonces, ¿qué pasa con a |op\\ b o a |op& b ?

"dolores de cabeza para los desarrolladores de analizadores" es la menor preocupación posible.

"dolores de cabeza para los desarrolladores de analizadores" es la menor preocupación posible.

Como desarrollador de analizadores, estoy inequívocamente de acuerdo con esto.

|> y <| son operadores infijos perfectamente buenos, pero no hay ningún beneficio en implementar la sintaxis general del operador usando otros dos operadores. Y se necesita decir mucho más sobre cuán detallada y poco atractiva es esa sintaxis.

no hay ningún beneficio en implementar la sintaxis del operador general usando otros dos operadores.

Para ser claros, la visión a largo plazo aquí es que habría f <| y binario, x |> f binario y $#$2 x |> f <| z #$ ternario, donde el primero es solo una función pero el segundo dos se implementan como transformaciones en el analizador.

La idea de que esto podría implementarse usando dos funciones ordinarias |> y <| es solo un puente temporal hacia esa visión.

Y se necesita decir mucho más sobre cuán detallada y poco atractiva es esa sintaxis.

Ese es un buen punto. ¿Qué tal reemplazar |> y <| con | y & ? Tienen sentido tanto en pareja como individualmente, aunque pueden ser un poco discordantes para un jugador de hockey.

Robar | y & para esto no sería una buena asignación de ASCII, y sospecho que muchos preferirían que los delimitadores fueran simétricos.

Si la gente quiere un operador ternario x |> f <| y por otras razones, está bien, pero creo que debería considerarse por separado. No estoy seguro de que el analizador deba transformar |> en un <| invertido. Otros operadores similares como < no funcionan de esa manera. Pero eso también es un tema aparte.

Robando ambos | y & para esto no sería una buena asignación de ASCII, y sospecho que muchos preferirían que los delimitadores fueran simétricos.

está bien.

Entiendo que > y < son difíciles de escribir. En términos de simetría y tipificación en un teclado estándar, supongo que lo más fácil podría ser algo como &% y %& , pero eso es muy feo, R paralelo o no. También vale la pena considerar /| y |/ .

...

No estoy seguro de que el analizador deba transformar |> en un <| invertido

Creo que has entendido mal. a |> b debe analizarse como b(a) . (La versión sin análisis especial sería ((x,y)->y(x))(a,b) , que se evalúa como lo mismo, pero con más gastos generales).

a |> b debe analizar a b(a)

Ah, está bien, lo tengo.

Creo que podríamos hablar de qué personajes usar durante años. Confiaría en @StefanKarpinski (como la persona más importante en esta conversación hasta ahora) para tomar una decisión, y estaría bien con eso. Incluso si es algo contra lo que me he opuesto (como a f b ).

Aquí hay algunas opciones para ver qué atrae:
a |>op<| b (dejando el actual |> sin cambios)
a |{ op }| b (cercano y en el mismo estado de cambio en muchos teclados comunes, no demasiado feo. Un poco extraño como independientes).
a \| op |\ b o a /| op |/ b o combinaciones de los mismos
a $% op %$ b (relativamente tipeable, inspirado en R. Pero un poco feo).
a |% op %| b
a |- op -| b
a |: op :| b
a | op \\ b
a | op ||| b
a op b

Stefan no es mayor que yo.

¡Parece que acabas de nominarte a ti mismo, entonces, para los poderes de BDFL en este tema! ;)

a @op@ b ?

Supongo que mi voto es usar los 4 de \| , |\ , /| y |/ . Abajo para evaluación, arriba para curry; barra hacia la función. Entonces:
a \| f (o f |/ a ) -> f(a)
a /| f (o f |\\ a ) -> (b...)->f(a,b...)
f |\ b (o b //| f ) -> (a...)->f(a...,b)
y por lo tanto:
a \| f |\ b (o a /| f |/ b ) -> f(a,b)
a \| f |\ b |\ c (o a /| b /| f |/ c ) -> f(a,b,c)

Cada uno de los 4 operadores principales, excepto quizás |/ , es útil por sí solo. La redundancia ciertamente no sería Pythonic, pero creo que la limpieza lógica es Julian. Y como cuestión práctica, puede usar cualquier versión del idioma infijo que le resulte más fácil de escribir; ambos son igualmente legibles, en el sentido de que una vez que has aprendido uno, naturalmente entiendes ambos.

Obviamente, tendría el mismo sentido si cambiara todas las barras, de modo que las flechas hacia arriba fueran para evaluación y hacia abajo para curry.

Todavía estoy esperando noticias de On High (y me disculpo por mi torpeza de novato al adivinar lo que eso significaba). Pero si alguien más alto que este cobertizo para bicicletas toma una decisión, para esta o cualquier otra versión con al menos dos símbolos nuevos, me complacería escribir un parche a corto plazo (usando funciones) y/o uno adecuado (usando transformaciones).

Intentamos evitar tener un BDFL en la medida de lo posible :)

Solo pensé en anotar algunas cosas rápidas.

Primero, el otro beneficio (los "usos independientes") de la notación que se propone es que <| se puede usar en otros contextos, de una manera que mejora la legibilidad. Por ejemplo, si tiene una matriz de cadenas, A , y quiere rellenarlas todas a la izquierda hasta 10, ahora mismo, debe escribir map(i->lpad(i,10),A) . Esto es relativamente difícil de leer. Con esta notación, se convierte en map(lpad<|10,A) , que creo que estará de acuerdo en que es significativamente más limpio.

Segundo, la idea detrás de esto es mantener la notación consistente. Ya existe un operador |> , que existe para cambiar el "arreglo" de una llamada de función de prefijo a sufijo. Esto solo extiende la notación.

Tercero, la posibilidad de usar el infijo directo como a f b tiene un problema mayor. a + b y a * b terminarían teniendo que tener la misma precedencia, ya que + y * son nombres de funciones, y sería inviable que el sistema tienen precedencia variable. Eso, o tendría que tratar a los operadores infijos existentes de manera diferente, lo que podría causar confusión.

Por ejemplo, si tiene una matriz de cadenas, A , y quiere rellenarlas todas a la izquierda hasta 10, ahora mismo, debe escribir map(i->lpad(i,10),A) . Esto es relativamente difícil de leer. Con esta notación, se convierte en map(lpad<|10,A) , que creo que estará de acuerdo en que es significativamente más limpio.

Enfáticamente no estoy de acuerdo. La sintaxis propuesta es, perdónenme, ensalada ASCII, al borde de algunas de las peores ofensas de Perl y APL, sin precedentes en otros idiomas para dar al lector casual una pista de lo que está sucediendo. La sintaxis actual, aunque tiene algunos caracteres más (¿cinco?), es bastante clara para cualquiera que sepa que i->expr es una sintaxis lambda, que es en un conjunto grande y creciente de idiomas.

a + b y a * b acabarían teniendo la misma precedencia, ya que + y * son nombres de funciones, y sería inviable que el sistema tuviera precedencia variable. Eso, o tendría que tratar a los operadores infijos existentes de manera diferente, lo que podría causar confusión.

No creo que esto sea un problema real; podemos simplemente decir cuál es la precedencia del infijo a f b y mantener todos los niveles de precedencia existentes también. Esto funciona porque la precedencia está determinada por el nombre de la función; cualquier función llamada "+" tendrá prioridad "+".

Sí, ya lo hacemos para la sintaxis 1+2 in 1+2 y no ha sido un problema.

No creo que esto sea un problema real; podemos simplemente decir cuál es la precedencia del infijo afb y mantener todos los niveles de precedencia existentes también. Esto funciona porque la precedencia está determinada por el nombre de la función; cualquier función llamada "+" tendrá prioridad "+".

No quise decir que es difícil escribir el analizador para que funcione. Quise decir que conduce a problemas de coherencia, por lo tanto, digo "o tendría que tratar a los operadores infijos existentes de manera diferente, lo que podría causar confusión". Entre otras cosas, considere que ¦ y no se ven tan diferentes en concepto, sin embargo, uno es un operador infijo predefinido, mientras que el otro no lo es.

Enfáticamente no estoy de acuerdo. La sintaxis propuesta es, perdónenme, ensalada ASCII, al borde de algunas de las peores ofensas de Perl y APL, sin precedentes en otros idiomas para dar al lector casual una pista de lo que está sucediendo. La sintaxis actual, aunque unos pocos caracteres más larga (¿cinco?), es bastante clara para cualquiera que sepa que i->expr es una sintaxis lambda, que es en un conjunto grande y creciente de idiomas.

Tal vez debería ser más claro en lo que estoy diciendo. Estoy diciendo que ser capaz de describir la operación como "lpad by 10" es mucho más claro de lo que lo hace i->lpad(i,10) . Y, desde mi punto de vista, lpad<|10 es lo más cercano que puede llegar a eso, en una forma no específica del contexto.

Tal vez ayudaría si describo de dónde vengo. Soy matemático y físico matemático, ante todo, y la "sintaxis lambda", aunque sensata desde el punto de vista de la programación, no es la más clara para aquellos que tienen menos experiencia en programación. Según tengo entendido, Julia tiene como objetivo principal ser un lenguaje informático científico, de ahí el gran parecido con MATLAB.

Debo preguntar: ¿cómo es lpad<|10 más "ensalada ASCII" que, digamos, x|>sin|>exp ? Sin embargo, se agregó la notación |> . Compáralo con, digamos, bash scripting, donde | se usa para pasar el argumento de la izquierda al comando de la derecha; si sabes que se llama "tubería", tiene _un poco_ más sentido, pero si Si no eres experto en programación, no va a tener sentido. En ese sentido, |> en realidad tiene más sentido, ya que se parece vagamente a una flecha. Y luego <| es una extensión natural de la notación.

Compare con algunas de las otras sugerencias, como %func% , que _sí_ tiene un precedente en otro idioma, pero que es completamente opaca para las personas que no tienen un amplio conocimiento de programación en el idioma.

Eso sí, miré un poco hacia atrás en una de las discusiones más antiguas, y veo que HA habido una notación utilizada en otro idioma que sería bastante agradable, en teoría. Haskell aparentemente usa a |> b c d para representar b(a,c,d) . Si los espacios que siguen al nombre de una función le permitieran especificar "parámetros" de esta manera, funcionaría bien - map(lpad 10,A) . El único problema surge con los operadores unarios: map(+ 10,A) produciría un error, por ejemplo, ya que se interpretaría en "+10" en lugar de i->+(i,10) .

En a f b : los problemas de precedencia pueden no ser tan malos como sugirió Glen-O, pero a menos que las funciones de infijo definidas por el usuario tengan la precedencia más baja, existen. Digamos, por el bien del argumento, les damos tiempos prec. En ese 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 => error de sintaxis?

Todo esto es una consecuencia natural de cómo escribirías el analizador, por lo que no es particularmente un dolor de cabeza en ese sentido. Pero no es particularmente intuitivo para el usuario ocasional del idioma.

Sobre la utilidad de un idioma de curry
Estoy de acuerdo con Glen-O en que (i)->lpad(i,10) es simplemente peor que lpad<|10 (o, si así lo elegimos, lpad |\ 10 , o lo que sea). El i es una carga cognitiva completamente extraña y una fuente potencial de errores; de hecho, juro que cuando estaba escribiendo eso hace un momento, involuntariamente escribí (i)->lpad(x,10) inicialmente. Entonces, tener una operación de curry infijo me parece una buena idea.
Sin embargo, si esa es la intención, cualquiera que sea el idioma infijo que elijamos, podemos crear nuestra propia operación de curry. Si es a f b , entonces algo como lpad rcurry 10 estaría bien. El punto es la legibilidad, no las pulsaciones de teclas. Así que creo que este es solo un argumento débil para <| .

En a |> b c d
Me gusta mucho esta propuesta. Creo que podríamos hacer que |> aceptara espacios a ambos lados, así que a b |> f c d => f(a,b,c,d) .

(Nota: Si tanto mi sugerencia de a b |> f c d como la de Glen-O de map(lpad 10,A) , esto crea un caso de esquina: (a b) |> f c d => f((x)->a(x,b),c,d) . Pero yo Creo que eso es tolerable.)

Esto todavía tiene problemas similares en términos de precedencia de operadores como a f b . Pero de alguna manera creo que son más tolerables si al menos puedes hablar de ellos en términos de la precedencia del operador |> , en lugar de ser la precedencia del operador ternario de con .

Pruebe lpad.(["foo", "bar"], 10) en 0.5. El |> existente no es exactamente querido por todos.

@tkelman : veo el problema, pero ¿cuál es tu punto? ¿Crees que deberíamos arreglar el |> existente antes de agregarle usos adicionales? ¿Si es así, cómo?

Personalmente, creo que deberíamos deshacernos del |> existente.

Pruebe lpad.(["foo", "bar"], 10) en 0.5. El |> existente no es exactamente querido por todos.

Creo que te has perdido el punto. Sí, la notación func.() es agradable y evita el problema en algunas situaciones. Pero uso la función map como una demostración simple. Cualquier función que tome una función como argumento se beneficiaría de esta configuración. Como ejemplo, simplemente para demostrar mi punto, es posible que desee ordenar algunos números en función de su mínimo común múltiplo con algún número de referencia. ¿Qué se ve mejor y más fácil de leer: sort(A,by=i->lcm(i,10)) o sort(A,by=lcm 10) ?

Me gustaría señalar una vez más que cualquier forma de definir operadores infijos permitirá crear un operador que haga lo que Glen-O quiere que haga <| , de modo que, en el peor de los casos, podrá escribir algo como sort(A,by=lcm |> currywith 10) . El objetivo de esta página es discutir cómo hacer a ... f ... b => f(a,b) . Entiendo que si los |> existentes o los <| propuestos son operadores que valen la pena tiene alguna relación con ese punto, pero tratemos de no desviarnos demasiado.

Personalmente, creo que la propuesta a |> b c es la mejor hasta ahora. Sigue una convención existente de Haskell; está lógicamente relacionado con el operador |> existente; es razonablemente legible y razonablemente fácil de escribir. El hecho de que sienta que naturalmente se extiende a otros usos es secundario. Si no está de acuerdo, al menos mencione sus sentimientos sobre el idioma principal, no solo los usos secundarios propuestos.

Quise decir que conduce a problemas de coherencia, por lo tanto, digo "o tendría que tratar a los operadores infijos existentes de manera diferente, lo que podría causar confusión".

Estoy de acuerdo en que es difícil decidir sobre la precedencia de a f b . Por ejemplo in claramente se beneficia de la precedencia de comparación, pero es bastante probable que muchas funciones que se usan como infijo no deseen la precedencia de comparación. Sin embargo, no veo ningún problema de consistencia. Diferentes operadores tienen diferentes precedencias. Agregar a f b no obliga a nuestra mano a darle a + y * la misma prioridad.

Tenga en cuenta que |> ya tiene prioridad junto a la comparación. Para cualquier otra precedencia, francamente, creo que los paréntesis están bien.

Si no está de acuerdo conmigo, y si estuviéramos usando a |> f b , entonces podría haber operadores similares |+> , |*> y |^> , que funcionaba igual que |> , pero tenía la precedencia de su operador central. Creo que es exagerado, pero es una posibilidad.

Otra posibilidad para resolver el problema de precedencia es usar una sintaxis para operadores de infijos personalizados que incluya paréntesis de algún tipo, por ejemplo, (a f b) .

Discusiones 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 y https://github.com/JuliaLang/julia/issues/15612.

Debo preguntar: ¿cómo es lpad<|10 más "ensalada ASCII" que, digamos, x|>sin|>exp? Sin embargo, se agregó la notación |>.

Me imagino que @tkelman discute

debemos deshacernos del existente |>.

en parte porque _tanto_ lpad<|10 como x|>sin|>exp se aventuran en el territorio de las ensaladas ASCII :).

Creo que el (a f b) de @toivoh , con paréntesis obligatorios, es la mejor propuesta hasta ahora.

Relacionado con https://github.com/JuliaLang/julia/issues/11608 (y por lo tanto también https://github.com/JuliaLang/julia/issues/4882 y https://github.com/JuliaLang/julia/pull /14653): Si (a f b) => f(a,b) , entonces tendría sentido si (a <strong i="8">@m</strong> b) => (<strong i="10">@m</strong> a b) . Esto permitiría reemplazar la lógica macro de caso especial existente para y ~ a*x+b con normal (y por lo tanto mucho más transparente) (y @~ a*x+b) .

Además, los "parens requeridos" podrían ser el idioma preferido para definiciones de infijo concisas. En lugar de decir (para usar un ejemplo estúpido) a + b = string(a) * string(b) , se le animaría (mediante las herramientas de pelusa o las advertencias del compilador) a decir (a + b) = string(a) * string(b) . Me doy cuenta de que esto no es en realidad una consecuencia directa de elegir la opción "se requieren paréntesis" para infijo, pero es un modismo conveniente que nos permitiría advertir a las personas que usan infijo en el LHS por error, pero despedir a las personas que lo hacen en propósito.

Actualmente, creo que si está aplicando un infijo de función (en lugar de un prefijo),
entonces es un operador, y debería verse y actuar como un operador.

Y tenemos soporte para operadores infijos definidos usando Unicode.
desde https://github.com/JuliaLang/julia/issues/552

Supongo que sería bueno tener eso exento para que pueda agregar las palabras clave como en la sugerencia original.
Entonces podríamos tener, por ejemplo, 1 ⊕₂ 1 == 0
Poder tener nombres arbitrarios para tu infijo parece un poco excesivo.

debe verse y actuar como un operador.

Estoy de acuerdo en que debería haber convenciones de nomenclatura sólidas para los operadores infijos. Por ejemplo: un carácter de Unicode, o termina en una preposición. Pero esas deben ser convenciones que se desarrollen orgánicamente, no requisitos impuestos por el compilador. Ciertamente, no creo que el #552 sea el final de la historia; si hay docenas de operadores codificados, debería haber una manera de agregar más programáticamente, aunque solo sea para crear prototipos de nuevas características.

...

Para mí, la propuesta (a f b) (y (a <strong i="10">@m</strong> b) ) está muy por encima del resto de las propuestas de este error. Estoy casi tentado a hacer un parche.

(a f b) => f(a,b)
(a f b c d) => f(a,b,c,d)
(a f) =>error de sintaxis
(a+2 f+2 b+2) => (f+2)(a+2,b+2)
(t1=a t2=f t3=b) => (t1=f)((t2=a),(t3=b)) (el espacio tiene la menor precedencia posible, como en las macros)

...

¿Sería inapropiado para mí enviar un parche?

No entendí los dos ú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))

Encuentro la sintaxis (a f b c d) muy extraña. Dado que 1 + 2 + 3 se puede escribir como +(1,2,3) , ¿no debería f(a,b,c) escribirse como (a f b f c) ?

En general, personalmente no estoy convencido de que Julia deba admitir operadores de infijos personalizados más allá de lo que se permite actualmente.

Puedo ver dos problemas con (a f b c d) .

Primero, será difícil de leer cuando tenga una expresión más complicada; una de las razones por las que los corchetes pueden ser frustrantes es que a menudo puede ser difícil saber, de un vistazo, qué corchetes se emparejan con qué otros corchetes. Es por eso que los operadores de infijos y postfijos ( |> ) son deseables en primer lugar. En particular, gusta la fijación posterior porque permite una lectura agradable y ordenada de izquierda a derecha sin tener que lidiar con corchetes cada vez.

En segundo lugar, no deja forma de hacer bien las cosas como hacerlo por elementos. Tengo entendido que f.(a,b) va a ser una notación en 0.5 para hacer que f opere de manera elemental en sus argumentos con la transmisión. No habrá una forma clara de hacer lo mismo con la notación infija, si es (a f b) . En el mejor de los casos, tendría que ser (a .f b) , que en mi opinión pierde la amabilidad de la simetría que .( ofrece con .+ y .* .

Compárese, por ejemplo, con el caso de querer utilizar el ejemplo de Haskell. shashi en #6946 señaló que tiene un equivalente aquí. En Haskell, escribirías circle 10 |> move 0 0 |> animate "scale" "ease" . Usando esta notación, esto se convierte en ((circle(10) move 0 0) animate "scale" "ease") , que no es más claro que animate(move(circle(10),0,0),"scale","ease") . Y si quisiera copiar su círculo en varios lugares, usando la notación |> , es posible que tenga circle 10 .|> copy [1 15 50] [3 14 25] . En mi opinión, esa es la mejor manera de implementar la idea, y luego, los corchetes cumplen su función normal de tratar los problemas de orden de operación.

Y como he señalado, a|>f b c tiene la ventaja de tener también una extensión natural que permite que la misma notación tenga más uso: f b c se analizaría como "función f con parámetros b y c establecidos), y por lo tanto sería equivalente a i->f(i,b,c) . Esto le permite funcionar no solo para infijar, sino para otras situaciones en las que es posible que desee pasar una función (especialmente una función incorporada) con parámetros (observando que el estándar es tener el objeto de la función primero).

El |> también deja claro cuál es la función. Si tuviera, digamos, (tissue wash fire dirty metal) , sería bastante difícil, de un vistazo, reconocer wash como la función. Por otro lado, tissue|>wash fire dirty metal tiene un gran indicador que dice " wash es la función".

Algunas de las últimas objeciones me suenan a decir "¡pero podrías abusar de esta característica!" Mi respuesta es: por supuesto que podrías. Ya podrías escribir código completamente ilegible usando macros si quisieras. El trabajo del analizador es permitir usos legítimos; para detener los abusos, tenemos convenciones/modismos y, en algunos casos, delinters. Específicamente:

No entendí los dos últimos casos:

Estos no pretenden de ninguna manera ser un ejemplo a seguir; simplemente están mostrando las consecuencias naturales de las reglas de precedencia. Creo que los dos últimos ejemplos calificarían como abuso de sintaxis, aunque (a^2 ಠ_ಠ b^2) => ಠ_ಠ(a^2,b^2) es lo suficientemente claro.

no debería f(a,b,c) escribirse como (afbfc)

Mi propuesta de (a f b c d) fue, francamente, una ocurrencia tardía. Creo que tiene sentido, y podría presentar ejemplos en los que sea útil, pero no quiero colgar esta propuesta sobre este tema si es controvertida.

[Por ejemplo:

  • f es un "método de objeto" de un objeto a, probablemente complicado, usando b, c y d, probablemente más simple.
  • f es un método de "transmisión natural" como push! ]

Si bien (a f b f c) tendría sentido si f fueran como + , creo que la mayoría de los operadores no son realmente como + , por lo que no debería ser nuestro modelo.

será difícil de leer cuando tengas una expresión más complicada

Nuevamente, mi respuesta sería, "así que no abuses".

Digamos que queremos alguna forma de escribir una expresión complicada como a / (b + f(c,d^e)) con el infijo f . En la propuesta de @toivoh , eso sería a / (b + (c f d^e)) . En el uso similar al de Haskell, sería a / (b + (c |> f d^e)) o, en el mejor de los casos, si se cambiara la precedencia |> para corregir este ejemplo en particular, a / (b + c |> f d^e) . Creo que el de @toivoh es igual de bueno aquí.

(tissue wash fire dirty metal)

Creo que la solución a esto son fuertes convenciones de nomenclatura para operadores infijos. Por ejemplo, si hubiera una convención de que los operadores de infijo deberían ser un carácter de Unicode, o terminar en una preposición o guión bajo, entonces lo anterior sería algo así como (tissue wash_ fire dirty metal) que es tan claro como esa expresión podría esperar ser. .

...

elemento sabio

Esta es una preocupación valida. (a .f b) es una mala idea, porque podría leerse como ((a.f) b) . Mi primera sugerencia es (a ..f b) pero no me hace muy feliz.

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

He usado jquery, así que definitivamente veo la ventaja de encadenar funciones de esa manera. Pero creo que no es el mismo problema que los operadores infijos. Usando la propuesta (a f b) , podrías escribir lo anterior como:

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

... que no es tan conciso como la versión de Haskell, pero sigue siendo bastante legible.

Tal vez pueda limitarse a solo tres cosas dentro de () :
(a f (b,c))
.(a f (b,c)) usando el operador .(

Finalmente, una respuesta al punto general:

En general, personalmente no estoy convencido de que Julia deba admitir operadores de infijos personalizados más allá de lo que se permite actualmente.

Obviamente todos tenemos derecho a nuestras opiniones. (No tengo claro si el pulgar hacia arriba se refería a esa parte del comentario, pero si es así, se triplica).

Pero mis contraargumentos son:

  • Julia ya tiene docenas de operadores infijos, muchos de ellos extremadamente especializados. Es inevitable que se propongan más. Cuando alguien dice "¿cómo puede tener pero no § ?", prefiero responder "hágalo usted mismo" y no "espere hasta que la próxima versión sea ampliamente adoptada".
  • Algo como (a § b) es eminentemente legible, y la sintaxis es lo suficientemente liviana como para aprender de uno o dos ejemplos.
  • No soy la primera persona en plantear este problema, y ​​no seré la última. Entiendo que los diseñadores de lenguajes deben ser muy, muy escépticos con respecto a las características progresivas (incorrectas), porque una vez que agrega una característica fea, es básicamente imposible corregirla más adelante. Pero como dije anteriormente, creo que (a f b) es lo suficientemente limpio como para que no te arrepientas.

Realmente no estoy seguro de la claridad de (a f b)

Aquí hay un posible caso de uso:
select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,'indianapolis')) orderby :emp_id));

Este es ciertamente un uso viable de las funciones infijas.
La función select es la función de identidad o envía la consulta creada a la base de datos.

¿Es este código claro?
Simplemente no lo sé.

.(a f b)

Sí, eso tiene sentido. Pero no es muy legible.

¿Es (a @. f b) más legible? Porque la macro @. para habilitar eso sería una sola línea.

[[[Ahora que lo pienso, si permitimos macros infix sin requerir paréntesis, @Glen-O podría usarlos para hacer lo que quiera: 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") . Creo que esa solución es más fea que (a f b) , pero al menos resolvería este error general en mi opinión.]]]

...

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

Definitivamente preferiría usar una macro para "dónde" para que la expresión condicional no tenga que citarse de manera extraña. Entonces:

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

Los paréntesis son levemente molestos, pero por otro lado no veo una forma razonable para que el analizador pueda lidiar con este tipo de expresión sin ellos.

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

Los paréntesis son levemente molestos, pero por otro lado no veo una forma razonable para que el analizador pueda lidiar con este tipo de expresión sin ellos.

Pensándolo bien, la precedencia en esa expresión es de derecha a izquierda. Entonces, usando macros infix, también podría 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)

o incluso:

<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

Entonces, aunque todavía me gusta (a f b) , empiezo a ver que las macros infijas también son una buena respuesta.

Aquí está la propuesta completa a través de ejemplos, seguida de las ventajas y desventajas:

usos principales:

  • 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 esquina: (no pretende ser un buen código, solo para mostrar cómo funcionaría el analizador)

  • t1=a <strong i="54">@m</strong> t2=b t3=c => <strong i="56">@m</strong> t1=a t2=b t3=c (aunque este no es un buen estilo de programación)
  • t1 + a <strong i="59">@m</strong> t2 + b => <strong i="61">@m</strong> t1+a t2+b (aunque este no es un buen estilo de programación)
  • a b <strong i="64">@m</strong> c => error de sintaxis (??)
  • a <strong i="67">@m</strong> b [c,d] => por favor no, pero <strong i="70">@m</strong> a b[c,d] (ETA: No, con el parche sale como <strong i="72">@m</strong> a b ([c,d]) que probablemente sea mejor).
  • 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] => mal estilo , use paréntesis para aclarar, pero [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)

ventajas:

  • defina macros infijas, obtenga funciones infijas de forma gratuita (con una sobrecarga única de evaluación de macros. No es una sobrecarga tan baja como la magia del analizador, pero es mucho mejor que tener llamadas de funciones adicionales en cada evaluación)
  • puede conducir a potentes DSL, como se ve en el ejemplo similar a SQL anterior
  • Elimina la necesidad de un operador |> separado, ya que es una macro de una sola línea. Del mismo modo para <| y el resto de las propuestas de @Glen-O.
  • explícito, por lo que el riesgo de ser utilizado por accidente es muy bajo, a diferencia (a f b)
  • Como se muestra, la macro @defineinfix podría permitir el uso abreviado de funciones, no de macros.

(Menores) Desventajas:

  • la precedencia y la agrupación parecen funcionar bien en la mayoría de los casos con RtoL, pero habría excepciones que requerirían paréntesis.
  • Creo que a @> f b o incluso a <strong i="112">@f</strong> b no es tan legible como (a f b) (aunque tampoco son demasiado horribles).

Dado lo activo que se ha vuelto este hilo, voy a recordarle a la gente mi preocupación original con este tema: los problemas relacionados con la sintaxis a menudo generan una gran cantidad de actividad, pero esa cantidad de actividad generalmente no guarda proporción con el valor a largo plazo. del cambio que se debate. En gran parte, eso se debe a que los hilos sobre sintaxis terminan siendo argumentos puros sobre gustos.

esa cantidad de actividad es generalmente desproporcionada

Lo siento. Probablemente soy el más culpable de entrar en ida y vuelta.

Por otro lado, creo que este hilo claramente ha hecho un progreso "utilizable". Cualquiera de las últimas sugerencias (a f b) o [ a @> f b , con a <strong i="10">@f</strong> b definible como acceso directo] es claramente superior en mi opinión a las sugerencias anteriores como a %f% b o a |> f <| b .

Aún así, creo que más comentarios de ida y vuelta probablemente no harán más progresos, y animo a las personas a usar el pulgar hacia arriba o hacia abajo de ahora en adelante a menos que tengan algo realmente nuevo que sugerir (que es, no sólo un cambio ortográfico a una propuesta existente). He añadido emoticones de "hurra" (cono explosivo) a las "propuestas votables". Si cree que no deberíamos tener una sintaxis especializada para funciones arbitrarias en posición infija, entonces rechace el error como un todo.

...

ETA: Creo que esta discusión ahora es lo suficientemente madura como para obtener una etiqueta decision .

Como referencia, (y esperaba que alguien más lo señalara).
Si desea incrustar una sintaxis similar a SQL, creo que la herramienta adecuada para el trabajo es Literales de cadena no estándar.
Como todas las macros, tienen acceso a todas las variables en el alcance cuando se les llama,
y le permiten especificar su propio DSL, con su propia elección de prioridad, y se ejecutan en tiempo de compilación.

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

esta mejor escrito

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

Los literales de cadena no estándar son una sintaxis muy poderosa.
No puedo encontrar buenos ejemplos de ellos que se usen para incrustar un DSL.
Pero pueden hacerlo.

Y en este caso creo que el resultado es mucho más limpio que cualquier operación infija que se pueda definir.
Aunque tiene la sobrecarga de tener que escribir su propio microparser/tokenizador.


Realmente no veo la necesidad de una etiqueta de decisión.
Esto no tiene implementación como PR, ni ningún prototipo utilizable.
que permite a la gente probarlo.
Contrasta con https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539 con sus 8 prototipos utilizables

Mis sentimientos hacia esto suben y bajan cada vez que leo el hilo. No creo que lo sepa realmente hasta que lo pruebe. Y ahora mismo ni siquiera sé para qué lo usaría. (A diferencia de algunas de las definiciones de |> y <| que he usado en F#)

Sintaxis similar a SQL, la herramienta adecuada para el trabajo es Literales de cadena no estándar

Ya sea que SQL se haga mejor con NSL o no, creo que hay un nivel de DSL que es lo suficientemente complejo como para que las macros en línea sean muy útiles, pero no tan complejo como para que valga la pena escribir su propio microparser/tokenizer.

Ahora mismo ni siquiera sé para qué lo usaría. (A diferencia de algunas de las definiciones de |> y <| que he usado en F#)

La propuesta de macro en línea permitiría a las personas, entre otras cosas, ejecutar sus propias macros similares a |> o <| , por lo que podría usarlas para cualquier cosa que haya hecho en F#.

(No quiero entrar en discusiones de ida y vuelta, pero respondí de todos modos debido a lo siguiente, y creo que la propuesta de macro en línea mata a varios pájaros con una piedra relativamente suave).

Realmente no veo la necesidad de una etiqueta de decisión.

Pregunté antes si era apropiado para mí crear un parche de analizador y nadie respondió. La única palabra sobre eso hasta ahora es:

No creo que haya mucho en el camino de la "innovación sintáctica" antes de Julia 1.0.

Lo que parecería argumentar en contra de hacer un parche ahora, ya que podría simplemente sentarse y pudrirse un poco. Sin embargo, ahora estás diciendo que no vale la pena tomar una decisión sobre esto (¿incluyendo la decisión de no decidir ahora mismo?) a menos que tengamos una "implementación como PR [o] prototipo utilizable".

¿Qué significa eso? (¿Qué es un PR?) ¿Haría el trabajo una macro que usara el carácter '@' en lugar del token @ , de modo que <strong i="22">@testinline</strong> a '@'f b => @f(a, b) ? ¿O debo enviar un parche a julia-parser.scm? (De hecho, comencé a buscar por primera vez escribir un parche de este tipo, y parece que debería ser simple, pero mi esquema está muy oxidado). ¿Necesito crear casos de prueba?

En este momento, hay 13 participantes en este error. Hay un total de 5 personas que votaron por una o más de las propuestas y/o rechazaron el error en sí, y solo uno de ellos (yo) lo hizo después de que la propuesta de macro en línea estuviera sobre la mesa. Eso no me hace confiar en que sea el momento de crear prototipos todavía. Cuando el número de personas que han votado desde la última propuesta seria sea más o menos la mitad del número de participantes, espero que se aclare algún tipo de consenso aproximado, y entonces será el momento de crear prototipos, probar y decidir (o, como puede ser el caso, renunciando a la idea).

Por "implementación como PR [o] prototipo utilizable".
Me refiero a algo con lo que se pueda jugar.
Entonces se puede ver cómo se siente en la práctica.

Un PR es una solicitud de extracción, por lo que un parche es el término que ha estado usando.

Si hiciste un PR, podría descargarse y probarse.
Sin embargo, más simple si lo implementaste con macros
o literales de cadena no estándar,
podría probarse sin tener que construir julia.

Como si no fuera mi decisión, pero dudo que pueda hacer mi propia opinión sin algo con lo que pueda jugar.

También +1 para no ir y venir del cobertizo de bicicletas.

...o tal vez un paquete Infix.jl con macros y literales de cadena no estándar.

Definitivamente hemos llegado al punto de "código de trabajo o GTFO" en esta conversación.

OK, aquí está el código de trabajo entonces: https://github.com/jamesonquinn/JuliaParser.jl

ETA: ¿Debería hacer referencia a una confirmación específica, o está bien el enlace anterior al último maestro?

...

(Eso no tiene ninguna de las macros de conveniencia que esperaría que quisiera, como los equivalentes para |> , <| , ~ , y el @defineinfix de mi ejemplo anterior. Tampoco elimina _deprecate_ la lógica de caso especial ahora inútil para ~ o el operador |> . Son solo los cambios del analizador para que funcione. probó la funcionalidad básica pero no todos los casos de esquina.

...

Creo que el truco feo actual con ~ muestra que hay un caso de uso claro para este tipo de cosas. Usando este parche, dirías @~ cuando necesites un comportamiento macro; mucho más limpio, sin ningún caso especial. ¿O alguien cree seriamente que ~ es completamente único y que nadie querrá volver a hacerlo nunca más?

Tenga en cuenta que el parche (todavía no es un PR porque se dirige al analizador de arranque nativo, pero por ahora el esquema uno debe ser lo primero en términos de PR) es más útil en general que el nombre del problema aquí. El nombre del problema es "operadores de infijos personalizados"; el parche proporciona macros infijas, con operadores infijos solo como un efecto secundario de eso.

El parche tal como está no es un cambio importante, pero espero que si este se convierte en el plan, el siguiente paso sería desaprobar los ~ y |> existentes actualmente, lo que eventualmente conduciría a rompiendo cambios.

...

Se agregaron algunas pruebas simples.

11608 se cerró con un consenso bastante claro de que muchos de nosotros no queremos macros infix y el único caso actual de análisis de ~ fue un error (cometido desde el principio para la compatibilidad con R y ninguna otra razón especialmente buena). Tenemos la intención de desaprobarlo y finalmente deshacernos de él, pero aún no lo hemos hecho (junto con el trabajo de modificar la API para la interfaz de fórmula en los paquetes de JuliaStats).

Las macros ahora son técnicamente genéricas, pero sus argumentos de entrada siempre son Expr , Symbol o literales. Por lo tanto, no son realmente extensibles a nuevos tipos definidos en paquetes de la forma en que lo son las funciones (infix o de otro tipo). Los posibles casos de uso para macros infix se atienden mejor con literales de cadena o DSL de macro anotados con prefijo.

(Lo siento, publiqué antes de tiempo; arreglado ahora).

En #11608, veo varios argumentos negativos:

===

¿En qué se transformaría lo siguiente?
...
y = 0.0 @in@ x == 1.0 ? 1 @in@ 2 : 3 @in@ 4

Esto fue tratado en el hilo:

En casos como ese, siempre uso paréntesis...

y

mismo precedente... se aplican sin ser macros: 0.0 in 1 == 1.0 ? 2 in 2 : 3 in 4

===

más funcionalidad para Julia que la gente tiene que implementar, mantener, probar, aprender a usar, etc.

que es (parcialmente) respondida (y secundada) aquí por:

"dolores de cabeza para los desarrolladores de analizadores" es la menor preocupación posible.

===

¿No hay forma de que 2 paquetes tengan simultáneamente definiciones para el mismo macrooperador que puedan usarse juntos sin ambigüedades en una base de código de usuario único?

Este es un punto interesante. Obviamente, si la macro solo llama a una función, entonces tenemos todo el poder de envío de la función. Pero si es una verdadera macro, como con ~ , entonces es más complicado. Sí, podría imaginar soluciones alternativas, como intentar llamarlo como una función y detectar cualquier error para usarlo como una macro ... pero ese tipo de fealdad no debe alentarse.

Aún así, esto es un gran problema para cualquier macro. Si dos paquetes exportan una macro, simplemente no puede tener ambos con "usar".

¿Es probable que esto sea más un problema con las macros infix? Bueno, depende de para qué las personas terminen usándolos:

  • Solo una forma de tener funciones infijas definidas por el usuario. En ese caso, no son peores que cualquier otra función; el envío funciona bien.
  • Como una forma de usar otros estilos de programación, usando operadores como |> y <| que @Glen-O analiza arriba. En ese caso, creo que rápidamente se desarrollarán convenciones comunes sobre qué macro significa qué, con pocas posibilidades de colisión.
  • Como una forma de hacer DSL de propósito especial, como el ejemplo de SQL anterior. Creo que estos se usarán en contextos específicos y la posibilidad de colisión no es tan mala.
  • Para cosas como ~ de R. Al principio, esto parece lo más problemático; en R, ~ se usa para varias cosas diferentes. Sin embargo, creo que incluso allí, es manejable, con algo como:

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

Entonces, la función ~ podría despacharse según el tipo de LHS, pero RHS siempre sería un Expr. Este tipo de cosas permitiría que coexistieran los principales usos que tiene en R (regresión y graficación), es decir, despachar correctamente a pesar de provenir de diferentes paquetes.

(nota: lo anterior ha sido editado. Inicialmente, pensé que una expresión R como a ~ b + c usaba el enlace de b y c a través de la evaluación perezosa de R. Pero no es así t; b y c son los nombres de las columnas en un marco de datos que se pasan explícitamente, no los nombres de las variables en el ámbito local que se pasan implícitamente).

===

La única forma de avanzar aquí sería desarrollar una implementación real.

que he hecho.

===

Las macros ahora son técnicamente genéricas, pero sus argumentos de entrada siempre son Expr, Symbol o literales. Por lo tanto, no son realmente extensibles a nuevos tipos definidos en paquetes de la forma en que lo son las funciones (infix o de otro tipo).

Esto se relaciona con el punto anterior. En la medida en que una macro infija llama a una función específica, esa función aún es extensible a través del envío de la manera normal. En la medida en que no llama a una función específica, está haciendo algo estructural/sintáctico (como lo que hace |> ahora) que no debe extenderse ni redefinirse. Tenga en cuenta que incluso si llama a una función, el hecho de que sea una macro aún puede ser útil; por ejemplo, puede citar algunos de sus argumentos, o procesarlos en devoluciones de llamada, o incluso interactuar simultáneamente con el nombre y el enlace de una variable, de una manera que no puede hacerlo una llamada de función directa.

===

Los posibles casos de uso para macros infix se atienden mejor con literales de cadena o DSL de macro anotados con prefijo.

Como se señaló en el hilo de referencia:

[Infix es] más fácil de analizar (para los hablantes de inglés y la mayoría de los occidentales), porque nuestro idioma funciona de esa manera. (En general, lo mismo se aplica a los operadores).

Por ejemplo, cuál es más legible (y escribible):

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)

o

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

?

===

Por fin:

11608 se cerró con un consenso bastante claro

Me parece bastante dividido en partes iguales, con "quién va a hacer el trabajo" emitiendo el voto decisivo. Lo cual ahora es, al menos en parte, discutible; He hecho el trabajo en JuliaParser y estaría dispuesto a hacerlo en Scheme si a la gente le gusta esta idea.

Esta es mi última publicación en este hilo, a menos que haya una reacción positiva a mi juliaparser pirateado. No es mi intención imponer mi voluntad; solo para presentar mi punto de vista.

Estoy argumentando a favor de las macros infijas ( a <strong i="6">@m</strong> b => <strong i="8">@m</strong> a b ). Eso no significa que no esté al tanto de los argumentos en contra. Así es como resumiría el mejor argumento en contra:

Las características del idioma comienzan en -100. ¿Qué ofrecen las macros infix que posiblemente podrían superar eso? Por su propia naturaleza, no hay nada que pueda lograr con las macros infijas que no pueda lograr con las macros prefijas.

Mi respuesta es: Julia es ante todo un lenguaje para programadores STEM. Matemáticos, ingenieros, estadísticos, físicos, biólogos, gente de aprendizaje automático, químicos, econometristas... Y una cosa que creo que la mayoría de esas personas se dan cuenta es la utilidad de una buena notación. Para tomar un ejemplo con el que estoy familiarizado en estadística: agregar variables aleatorias independientes es equivalente a convolucionar archivos PDF, o incluso a convolucionar derivados de CDF, pero a menudo expresar algo usando el primero puede ser un orden de magnitud más conciso y comprensible que el último. .

Infijo versus prefijo versus posfijo es, hasta cierto punto, una cuestión de gusto. Pero también hay razones objetivas para preferir el infijo en muchos casos. Mientras que el prefijo y el posfijo conducen a precipitados indigeribles de operadores consecutivos como los que hacen que los programadores de Forth suenen como políticos alemanes, o los que hacen que los programadores de Lisp suenen como una caricatura de Chomski, el infijo coloca a los operadores en lo que a menudo es más cognitivamente lugar natural, lo más cerca posible de todos sus operandos. Hay una razón por la que nadie escribe trabajos de matemáticas en Forth, y por la que incluso los matemáticos alemanes usan operadores infijos cuando escriben ecuaciones.

Sí, las macros infix podrían usarse para escribir código ofuscado. Pero las macros de prefijos existentes son igualmente propensas al abuso. Si no se abusa de ellas, las macros infix pueden conducir a un código mucho más claro .

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

Me doy cuenta de que estos son solo ejemplos de juguetes, pero creo que el principio es válido.

¿Se podría hacer lo anterior con literales de cadena no estándar? Bueno, el segundo y tercer ejemplo funcionarían como NSL. Pero el problema con las NSL es que le dan demasiada libertad: a menos que esté familiarizado con la gramática en particular, no hay forma de estar seguro de cuáles son los tokens de una NSL, y mucho menos su orden de operaciones. Con las macros infix, tiene suficiente libertad para hacer todos los ejemplos anteriores, pero no tanta como para que no quede claro al leer el código "bueno" cuáles son los tokens y dónde van los paréntesis implícitos.

El necesita que ciertas cosas se muevan de incógnitas desconocidas a incógnitas conocidas. Y desafortunadamente, no hay un mecanismo para hacer esto. Tus argumentos necesitan una estructura que no existe.

Ahora que <| es asociativo por la derecha (#24153), ¿funciona la propuesta inicial a |>op<| b ?

No sé a cuántos posibles operadores de infijos afecta esto, pero realmente me gustaría usar <~ . El analizador no cooperará, incluso si espacio las cosas con cuidado, quiere que a <~ b signifique a < (~b) .

<- tiene un problema similar.

Lo siento si esto ya está cubierto por este u otro problema, pero no pude encontrarlo.

Potencialmente podríamos requerir espacios en a < ~b ; Hemos agregado reglas como esa antes. Entonces podríamos agregar <- y <~ como operadores infijos.

Gracias @JeffBezanson , ¡sería genial! ¿Sería este un caso especial o una regla más general? Estoy seguro de que hay algunos detalles en lo que debería ser la regla para permitir más operadores infijos, dar un código claro y predecible y romper la menor cantidad posible de código existente. De todos modos, agradezco la ayuda y la rápida respuesta. ¡Feliz año nuevo!

En caso de que a <~ b sea ​​diferente de a < ~b me gustaría ver a =+ 1 como error (o advertencia al menos)

Sé que esta es una discusión bastante antigua, y la pregunta se hizo hace bastante tiempo, pero pensé que valía la pena responderla:

Ahora que <| es asociativo por la derecha (#24153), ¿funciona la propuesta inicial a |>op<| b ?

No, desafortunadamente, |> todavía tiene prioridad. La actualización realizada hace que, si define <|(a,b)=a(b) , entonces puede hacer con éxito a<|b<|c para obtener a(b(c)) ... pero este es un concepto diferente.

Frozen durante 2 años, un comentario y un commit hace 2 y 5 días!

Ver Documento Operadores binarios personalizables f45b6be

¿Fue útil esta página
0 / 5 - 0 calificaciones