Julia: Propuesta: Desechar y luego eliminar la tubería de función

Creado en 30 ene. 2017  ·  37Comentarios  ·  Fuente: JuliaLang/julia

Propuesta

Desaprobar el uso actual de |> como conducto de funciones. Es decir, la sintaxis x |> f quedaría en desuso en favor de la sintaxis de llamada normal f(x) . Después del período de obsolescencia, Base.:(|>) no estaría definido.

Este cambio fue sugerido inicialmente por tkelman en https://github.com/JuliaLang/julia/issues/16985#issuecomment -227015399.

Ha habido mucho debate polémico sobre varias sintaxis para canalización de funciones (en particular, vea #5571), con argumentos para imitar una variedad de lenguajes. Esa discusión se ha tenido _ad nauseum_ y no deseo repetirla. Ese NO es el propósito de esta propuesta.

Razón fundamental

Varios paquetes bien pensados ​​y bien mantenidos han implementado macros que proporcionan una sintaxis de canalización conveniente para una variedad de casos de uso, tanto generales como específicos. Los ejemplos incluyen Lazy.jl , FunctionalData.jl , Pipe.jl y ChainMap.jl , entre otros.

StefanKarpinski y andyferris nos dieron una composición de funciones arbitrarias en #17155, que puede tener un propósito similar en muchas situaciones.

Como tkelman argumentó de manera similar en # 5571, la canalización de funciones en Base está al revés de la sintaxis de llamada familiar; tener ambos en el idioma base es esencialmente respaldar el uso de 2 sintaxis diferentes para lograr el mismo objetivo. Si bien a menudo hay varias formas de escribir lo mismo usando soluciones en Base, por lo general, las soluciones al menos se adhieren a un modelo mental similar. En este caso, las sintaxis emplean modelos mentales literalmente opuestos.

Las canalizaciones de funciones violan el principio de menor sorpresa al aplicar la acción después del objeto. Es decir, si lee sum(x) sabe inmediatamente cuando ve sum() que va a sumar los valores en el argumento. Cuando ve x |> sum , ve x , y de repente está sumando sus valores. Pocas, si es que alguna, otras soluciones de Base ponen la acción al final, lo que hace que la canalización sea la extraña.

De hecho, la canalización tiene un precedente en otros idiomas, por ejemplo, %>% de Hadley Wickham en R (que no forma parte de la base R), ya veces ese estilo/flujo tiene sentido. Sin embargo, en aras de la coherencia dentro de Base Julia, propongo que aplacemos la responsabilidad de proporcionar sintaxis de canalización a los paquetes, que pueden redefinir |> o proporcionar macros convenientes según lo consideren adecuado.

Elementos de acción

Si se acepta esta propuesta, los puntos de acción serían:

  • [ ] Eliminar usos de la sintaxis dentro de Base, si existe alguno
  • [ ] Proporcione una desaprobación formal por Base.:(|>) en 0.6 o 1.0
  • [ ] Eliminarlo en una versión posterior
deprecation design julep

Comentario más útil

Si estamos desaprobando esto, ¿deberíamos también desaprobar * para la concatenación de cadenas? Eso tiene problemas similares, ya que es redundante con string(a, b) y viola el principio de menor sorpresa dado que a y b no son números.

De manera más general, probablemente deberíamos desaprobar toda la notación infija, ya que es confuso tener varias convenciones de llamada como *(a, b) frente a a * b : podemos reducir nuestras 3 sintaxis dispares actuales a una y obtener una consistencia total. Para evitar la fealdad, podríamos considerar mover la llamada de función dentro de los paréntesis, y quizás también deshacernos de las comas redundantes.

Todos 37 comentarios

La canalización de funciones proporciona una sintaxis de postfijo para la llamada de funciones, lo cual es conveniente en el REPL para la generación de datos interactivos y una mayor visualización/resumen.

Un caso de uso que he visto escribir a muchas personas es

julia> somecomplicatedthingproducingarray
...

<ARROW UP>

julia> somecomplicatedthingproducingarray |> summarize

donde la función summarize es algo así como un diagrama o histograma

@jiahao No estoy argumentando que no sea útil, sino que debemos ser coherentes dentro de Base y dejar que los paquetes proporcionen cosas como esta.

también hay ans para el uso de reemplazo

En esta propuesta, ¿ |> todavía se analizaría como un operador infijo?

@ajkeller34 : definitivamente, los paquetes serían libres de hacer lo que quisieran con él (aunque tendrían que jugar bien entre sí en términos de tipo de piratería y coexistencia), sin tanta restricción de ser semánticamente compatible con el antiguo definición básica.

Eliminar usos de la sintaxis dentro de Base, si existe alguno

Aquí hay un intento ahora muy desactualizado que hice para hacer esto: https://github.com/tkelman/julia/commit/212727cdc4aaa3221763580f15d42cfe198bcc1c
En ese momento, la mayoría de los usos de la base eran bastante triviales. Algunos de los usos de las pruebas de "canalizar esta cosa a esta función anónima" son quizás más agradables con la canalización, pero dado que la mayoría de ellos estaban reutilizando la misma función anónima varias veces, probablemente valdría la pena darle un nombre y llamarlo como un función normal en ese punto.

En caso de que alguien tenga curiosidad, tengo ChainRecursive.jl ahora. Pondré un anuncio en el discurso sobre la desintegración de ChainMap.jl y sus diversos hijos una vez que esté completo.

Permítanme ofrecer algo de resistencia aquí ya que tengo cierto interés personal y un gusto particular por lo que |> hace posible.

Estoy de acuerdo con @jiahao en que |> es muy útil cuando quieres probar cosas rápidamente en REPL. Además, también lo encuentro útil cuando su argumento es demasiado grande o amerita algo de equilibrio (sí, lo dije) . En el caso del ejemplo vinculado, de hecho es mejor que el argumento sea más prominente que la función que se llama. sum(x) es un ejemplo demasiado simple y, de hecho, debería escribirse como sum(x) ). En Escher.jl todas las funciones que agregan propiedades a los elementos tienen un método curry. Esto encaja muy bien con |> (eso fue planeado, también funciona muy bien con map ) y es un placer poder probar cosas al final de la línea y ver la actualización de la interfaz de usuario. inmediatamente. No tengo que encontrar mi camino hasta el comienzo de la expresión y perder el tiempo. Para usar con Escher al menos, la alternativa sugerida es asignar expresiones grandes a variables de nombres inventados como padded_box_contents_aligned_right_tomato_background (o peor box34 ) y luego llamar a una función en ellos. A diferencia de la bella lectura <big UI expression> |> aligncontents(right) |> pad(1em) |> fillcolor("tomato")

Sé que después de esto puedo definir |> dentro de Escher y probablemente lo haré, pero me matará el cerebro ver WARNING: using Escher.|> in module YourPackage conflicts with an existing identifier. Los paquetes casi definitivamente le darán diferentes significados a esto, lo cual para mí es muy ¡alarmante!

StefanKarpinski y andyferris nos dieron una composición de funciones arbitrarias en #17155, que puede tener un propósito similar en muchas situaciones.

¿La alternativa a box |> fill("orange") |> pad(2em) sería (fill("orange") ∘ pad(2em))(box) en lugar de box |> fill("orange") ∘ pad(2em) ? Estos dos parecen ortogonales.

Me parece que el uso de Escher de cierres como objetos está definiendo un DSL solo por usar esta sintaxis (que tiene serias limitaciones para cualquier cosa que no sea de entrada única, salida única), donde probablemente estaría mejor servido , y más generalizable, si utilizó una de las múltiples macros de encadenamiento disponibles.

Eliminar la definición de Base de esto permitiría a las personas a las que les gusta esta sintaxis hacer cosas más interesantes con ella.

@shashi Entiendo sus puntos, pero podría obtener el mismo comportamiento usando uno de los paquetes que cité en el problema, ¿no es así? Como ejemplo, en su ejemplo de Escher, podría usar FunctionalData para hacer <strong i="6">@p</strong> vbox(<really big thing>) | pad(2em) o Lazy para hacer @> vbox(...) pad(2em) .

Eliminar la definición de Base de esto permitiría a las personas a las que les gusta esta sintaxis hacer cosas más interesantes con ella.

Excepto que no se podrá usar, ya que la única forma segura de usarlo sería Escher.|>(...) o Lazy.|>(...) .

Hipotéticamente, ¿cómo se usaría |> como operador infijo si está usando dos paquetes diferentes que lo definen y lo exportan, asumiendo que no está definido en Base ?

@kmsquire Depende del caso de uso. |> todavía se analizaría como un operador infijo tal como es ahora, simplemente no tendría un valor en Base. Si lo usa en una macro, no importa cómo lo defina un paquete en particular, ya que simplemente se convierte en el primer argumento en una expresión de llamada.

Tomemos, por ejemplo <| , que se analiza como un operador infijo pero no tiene un valor. Aunque no está definido, todavía tenemos

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

Los paquetes pueden definir y exportar métodos para Base.:(<|) que significan cosas diferentes, tal como se puede hacer con + .

Pero los paquetes que proporcionan una buena canalización de funciones lo hacen en macros, supongo que precisamente por esta razón.

FWIW, ningún paquete de encadenamiento necesitaría usar |> durante la evaluación porque durante el encadenamiento todo se comprime en una sola expresión. Me imagino que si los paquetes van definiendo |> , será precisamente la definición en la base. Aunque probablemente deberían usar una macro de encadenamiento en su lugar. Consulte DataFramesMeta para ver un buen ejemplo de cómo crear una interfaz que funcione bien con el encadenamiento.

Si estamos desaprobando esto, ¿deberíamos también desaprobar * para la concatenación de cadenas? Eso tiene problemas similares, ya que es redundante con string(a, b) y viola el principio de menor sorpresa dado que a y b no son números.

De manera más general, probablemente deberíamos desaprobar toda la notación infija, ya que es confuso tener varias convenciones de llamada como *(a, b) frente a a * b : podemos reducir nuestras 3 sintaxis dispares actuales a una y obtener una consistencia total. Para evitar la fealdad, podríamos considerar mover la llamada de función dentro de los paréntesis, y quizás también deshacernos de las comas redundantes.

|> todavía se analizaría como un operador infijo tal como es ahora, simplemente no tendría un valor en Base. Si lo usa en una macro, no importa cómo lo defina un paquete en particular

Todavía no estoy seguro de por qué necesitamos eliminarlo de Base.

@bramtayl hace un buen punto:

Me imagino que si los paquetes van definiendo |> será precisamente la definición base.

Y aún así, la única forma de usar más de un paquete que define esto es no usarlo como infijo.

No veo por qué se requiere eliminar la definición en Base para que |> se use dentro de las macros.

no lo es Mi punto es que |> se puede usar dentro de macros independientemente de la situación en Base. Lo mismo ocurre con cualquier operador que analice adecuadamente. El objetivo de la propuesta es hacer que Base sea autoconsistente en términos de llamadas a funciones, luego el comportamiento de canalización se puede lograr a través de paquetes. No importa si los paquetes usan |> en particular; podrían usar <| o literalmente cualquier otro operador infijo.

@ararslan cierto, eso no era lo que quería preguntar, actualicé mi comentario justo después, lo siento.

De todos modos, no entiendo del todo el sentimiento de "Base autónoma en términos de llamadas a funciones". Parece que esto solo hará que sea más difícil usar |> en un contexto que no sea macro. Personalmente, creo que |> es algo que vale la pena aprender para un novato, a pesar de que es sorprendente. Al menos ahorra esfuerzo en el REPL. Es bastante divertido darse cuenta más tarde de que |> es una función como cualquier otra función infija y refuerza la lección de que las funciones son solo valores.

Tal vez solo algo formal: discuta / decida las obsolescencias y / o los cambios de sintaxis al comienzo de un ciclo de lanzamiento, no al final. Actualmente, todos los principales desarrolladores y responsables del paquete dedican tiempo y energía a terminar 0.6 y es posible que no tengan tiempo para pensar en otra (buena) idea.

"No digo que no sea útil, sino que debemos ser coherentes"

¿A veces la utilidad supera a la consistencia? No estaba al tanto de la inconsistencia, pero encontré útil la sintaxis |> . Si se quita, no sentiré que he ganado nada tangible.

Una explicación de mi voto negativo, si se me permite:

Mucho de lo que está actualmente en Base podría suceder en paquetes. ¿Deberíamos mover los diccionarios a un paquete? ¿Quizás enumerar operaciones como ordenar y mezclar? ¿Operaciones de cobranza, etc.? Estoy seguro de que ha habido discusiones largas y detalladas sobre lo que debe y no debe incluirse en Base, pero supongo que hay tres razones por las que algunas funciones podrían incluirse en Base:
1) Esa funcionalidad es necesaria para habilitar otras funciones en la base.
2) Esa funcionalidad es una parte esencial del lenguaje, muchos programadores y paquetes de Julia la usarán y, por lo tanto, es deseable tener una implementación/sintaxis única en la que todos estén de acuerdo, en lugar de la fragmentación de muchas personas implementando la suya.
3) Incluir esa funcionalidad en la base hace que Julia "en bruto" sea más agradable de usar o hace que se sienta con más funciones, lo que ayuda con la evangelización y la adopción del idioma.

Algo así como sum probablemente alcance los 3 puntos, y yo diría que la canalización de funciones alcanza el segundo y tercer punto:

Tanto en la propuesta inicial (bien escrita) como en la discusión de este hilo, un tema común es la existencia de varios paquetes que brindan una funcionalidad similar a la de una tubería a través de macros: Lazy.jl, Pipe.jl, ChainMap.jl, etc. La existencia de múltiples paquetes sugiere fuertemente que muchas personas en la comunidad consideran que la canalización es una característica útil y deseable, y la presencia de estos paquetes en este hilo de discusión sugiere que muchas personas aquí entienden y apoyan el uso de canalización.

Dado que la canalización es una característica común y popular en la comunidad de Julia y otros idiomas, incluso en esta discusión la gente parece estar de acuerdo en que tiene muchos usos, especialmente en el REPL (donde Julia brilla), y ya hay fragmentación en el ecosistema de Julia. ..mi lectura no es que deba eliminarse de Base, sino que la sintaxis de canalización disponible en Base debe mejorarse para que haya menos necesidad de fragmentación. Diferentes paquetes que ofrecen diferentes formas de, por ejemplo, trazar parece estar bien; diferentes paquetes populares que ofrecen diferentes formas de aplicar funciones parece bastante aterrador.

Además, argumento que eliminar las tuberías de Base pero dejar el operador infijo es bastante sorprendente: en Julia no puede definir sus propios operadores infijos, pero hay un operador infijo sin usar |> dando vueltas que puede definir como ¿usted por favor? Si esa es una buena funcionalidad, ¿por qué no darnos 10 o 20 operadores infijos sólidos para definir como queramos?

Por último, creo que es natural seguir canalizando exactamente porque es diferente de la aplicación de otras funciones. Es una característica, no un error, que es diferente de otras convenciones de aplicación de funciones; esta diferencia es lo que le permite brillar en algunos casos de uso. Y hay otros casos en los que (agitando un poco la mano) el sustantivo viene antes del verbo, y muchos de estos son exactamente azúcar sintáctico en los casos en que la aplicación de la función en bruto es difícil de manejar. En la parte superior de mi cabeza, la tarea x = 5 es poner el sustantivo (símbolo x ) antes del verbo (vincular a un valor). Del mismo modo para acceder a campos de tipo t.a en lugar de getfield . Y lo más profundo, la indexación de matrices z[5] se lee como "de z tomar el 5.° elemento" y generalmente es más natural que getindex(z, 5) .

Si esa es una buena funcionalidad, ¿por qué no darnos 10 o 20 operadores infijos sólidos para definir como queramos?

Probablemente haya más que eso si uno incluye todos los Unicode además de los ASCII no reclamados como <| , ++ , ...

No estoy leyendo todo el hilo, pero solo quería decir
que me encanta poder canalizar. Yo votaría útil sobre
consistencia cualquier día.

Tengo una preferencia muy leve por mantenerlo, pero realmente no me importa mientras siga siendo un operador infijo. Siento que probablemente no usaría la canalización de funciones si implicara importar un paquete, lo que me dice que no lo valoro mucho.

Dicho esto, no creo que este argumento del "principio de la menor sorpresa" sea convincente, ya que hace algunas suposiciones sobre una base de usuarios diversa. Para los hablantes nativos de lenguajes sujeto-objeto-verbo, supongo que la mayor parte de la sintaxis de Julia viola el principio de menor sorpresa, y la canalización de funciones es bastante cómoda...

No leer todo el hilo

😕

Me encanta poder canalizar

Nuevamente, no estoy argumentando que uno no debería poder canalizar, sino que la funcionalidad podría obtenerse fácilmente en uno de los varios paquetes de canalización existentes. La eliminación de la canalización de Base permite que los paquetes definan más fácilmente su propia semántica de canalización sin tener que adherirse o ser coherente con lo que proporciona Base.

en Julia no puedes definir tus propios operadores infijos

Eso no es cierto; cualquier cosa que se analice como un operador infijo se puede definir o redefinir. Como señaló Martinholters, <| y ++ están igualmente disponibles, entre otros.

Soy un poco neutral en este caso, pero apoyaré el sentimiento de que |> estar al revés de la sintaxis de llamada de función normal es el punto central. Incluso los mayores fanáticos de las tuberías no están pidiendo (AFAIK) por ejemplo sin <| x porque eso realmente es redundante con sin(x) . |> es para aquellos casos en los que es más fácil para los ojos y/o el cerebro pensar en datos que fluyen de izquierda a derecha sin muchos paréntesis.

Me gustaría que |> sea ​​más potente, por ejemplo, permita x |> f(_) + 2g(_) |> h etc. y que no sea solo un operador. Cada vez que alguien define x |> f para que signifique algo además f(x) realmente me sorprende porque el punto central del operador tal como lo hemos usado es que es una sintaxis de llamada de orden diferente. Dado que podemos sobrecargar las llamadas, no veo una buena razón para que x |> f signifique otra cosa.

@StefanKarpinski Ya se pueden obtener tuberías más poderosas usando macros. Consulte, por ejemplo, Pipe.jl , que proporciona exactamente la sintaxis que está describiendo. Siempre que |> sea ​​un operador (personalmente, no veo que |> sea ​​un caso especial), las macros pueden usar cualquier delimitador de canalización que analice el infijo, incluso si no es un :call . Como ejemplo, se podría usar de manera similar @~ para canalizar (al menos a partir de este escrito). Ese nivel de flexibilidad es una de las ventajas de usar macros en Julia.

Podríamos agregar la funcionalidad de Pipe.jl al lenguaje, y luego lo tendría sin necesidad de escribir @pipe .

La razón principal para desaprobar |> sería si queremos reclamar la sintaxis para algún otro propósito que a la gente le guste mucho más.

Supongo que estoy tratando de argumentar que la canalización no necesita ser parte del lenguaje, puede (y ya lo hace) vivir en un paquete.

Pero si no hay nada más por lo que queramos |> , veo poco daño en dejar su definición (trivial) en paz.

No creo que actualmente haya ninguna propuesta para reutilizar |> en Base. Mi argumento para no definirlo en Base es que nos da más consistencia sin pérdida de funcionalidad.

¿Se simplificarían las propuestas de "tuberías más poderosas" o las implementaciones de paquetes al no tener que preocuparse por esta definición existente o solucionarla?

@ararslan "Eso no es cierto; cualquier cosa que se analice como un operador infijo se puede definir o redefinir".

Del manual "&& y || operadores", se analizan pero no se pueden redefinir (es algo bueno). Creo que las únicas excepciones.

Los llamados "operadores lógicos" && y || son infijos. [relación binaria unaria] "operador" es en mi humilde opinión el término incorrecto para ellos, ya que no lo son. No es una forma similar a la lógica bit a bit & y | que permiten la sobrecarga (algo que no estoy seguro de que sea una buena opción).

@PallHaraldsson Esos son flujo de control, no operadores en el mismo sentido que & , | , + , etc.

Tratemos de permanecer en el tema aquí si es posible, por favor.

@tkelman Ese es un buen punto. Sin embargo, sospecho que podemos hacer que la sintaxis de tubería futura sea compatible con versiones anteriores. Por ejemplo, si _ está reservado, entonces |> puede tener un significado especial cuando sus argumentos contienen _ y, de lo contrario, hacer lo mismo que hace ahora.

Hay otro problema: para hacer que |> funcione para su objeto, ¿define |> o el "operador de llamada de función" (es decir, agregarle métodos)? Podría ser más limpio si |> fuera una sintaxis incorporada para la llamada de función, para garantizar que f(x) y x |> f sean siempre iguales.

El consenso aquí está muy claramente en contra, así que seguiré adelante y cerraré el tema. Aprecio la discusión, todos.

Sé que este tema está cerrado. Solo quería decir "gracias" por mantener al operador.

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