Julia: Encadenamiento de funciones

Creado en 27 ene. 2014  ·  232Comentarios  ·  Fuente: JuliaLang/julia

¿Sería posible permitir llamar a cualquier función en Any para que el valor se pase a la función como primer parámetro y los parámetros pasados ​​a la llamada de función en el valor se agreguen después?
ex.

sum(a::Int, b::Int) -> a + b

a = 1
sum(1, 2) # = 3
a.sum(2) # = 3 or
1.sum(2) # = 3

¿Es posible indicar de forma determinista qué devolverá una función para evitar excepciones en tiempo de ejecución?

Comentario más útil

Entonces, nuestra lista actual de varios esfuerzos, etc.
Creo que vale la pena que la gente los revise (idealmente antes de opinar, pero w / e)
todos son ligeramente diferentes.
(Estoy intentando ordenar cronológicamente).

Paquetes

Prototipos sin paquete

Relacionado:


Quizás esto debería editarse en una de las publicaciones principales.

actualizado: 2020-04-20

Todos 232 comentarios

La sintaxis . es muy útil, por lo que no vamos a convertirla en un sinónimo de llamada de función. No entiendo la ventaja de 1.sum(2) sobre sum(1,2) . A mí me parece que confunde las cosas.

¿La pregunta sobre excepciones es un tema aparte? Creo que la respuesta es no, además de envolver un cuerpo de función en try..catch.

El ejemplo 1.sum (2) es trivial (yo también prefiero sum (1,2)) pero es solo para demostrar que una función no es propiedad per se de ese tipo ex. 1 se puede pasar a una función con el primer parámetro siendo un Real, no solo a las funciones que esperan que el primer parámetro sea un Int.

Editar: Es posible que haya entendido mal tu comentario. Las funciones de puntos serán útiles al aplicar ciertos patrones de diseño, como el patrón de construcción que se usa comúnmente para la configuración. ex.

validate_for(name).required().gt(3) 
# vs 
gt(required(validate_for(name)), 3) 

Las excepciones a las que me refería se deben a que las funciones devuelven resultados no deterministas (que de todos modos es una mala práctica). Un ejemplo sería llamar a.sum (2) .sum (4) donde .sum (2) a veces devuelve un String en lugar de un Int, pero .sum (4) espera un Int. Supongo que el compilador / tiempo de ejecución ya es lo suficientemente inteligente como para evaluar tales circunstancias, que sería lo mismo al anidar la función sum (sum (1, 2), 4), pero la solicitud de función requeriría extender dicha funcionalidad para hacer cumplir las restricciones de tipo en funciones de puntos.

Uno de los casos de uso que a la gente parece gustarle es la "interfaz fluida". A veces es bueno en las API de OOP cuando los métodos devuelven el objeto, por lo que puede hacer cosas como some_obj.move(4, 5).scale(10).display()

Para mí, creo que esto se expresa mejor como composición de funciones, pero |> no funciona con argumentos a menos que use anon. funciones, por ejemplo, some_obj |> x -> move(x, 4, 5) |> x -> scale(x, 10) |> display , que es bastante feo.

Una opción para respaldar este tipo de cosas sería si |> empujara el LHS como el primer argumento al RHS antes de evaluar, pero luego no podría implementarse como una función simple como lo es ahora.

Otra opción sería algún tipo de macro @composed que agregaría este tipo de comportamiento a la siguiente expresión

También puede transferir la responsabilidad de respaldar esto a los diseñadores de bibliotecas, donde podrían definir

function move(obj, x, y)
    # move the object
end

move(x, y) = obj -> move(obj, x, y)

por lo que cuando no proporciona un objeto, hace una aplicación de función parcial (devolviendo una función de 1 argumento) que luego podría usar dentro de una cadena |> normal.

En realidad, la definición de |> probablemente podría cambiarse ahora mismo a la
comportamiento que estás pidiendo. Yo estaría a favor.

El lunes 27 de enero de 2014, Spencer Russell [email protected]
escribió:

Uno de los casos de uso que a la gente parece gustarle es la "interfaz fluida". Sus
a veces es agradable en las API de programación orientada a objetos cuando los métodos devuelven el objeto, por lo que puede hacer
cosas como some_obj.move (4, 5) .scale (10) .display ()

Para mí, creo que esto se expresa mejor como composición de funciones, pero
el |> no funciona con argumentos a menos que use anon. funciones, por ejemplo, some_obj
|> x -> move (x, 4, 5) |> x -> scale (x, 10) |> display, que es bonito
feo.

Una opción para apoyar este tipo de cosas sería si |> empujara el LHS como
el primer argumento a la RHS antes de evaluar, pero luego no podría ser
implementado como una función simple como lo es ahora.

Otra opción sería algún tipo de macro @composed que agregaría esto
tipo de comportamiento a la siguiente expresión

También puede transferir la responsabilidad de apoyar esto a la biblioteca
diseñadores, donde podrían definir

función mover (obj, x, y)
# mover el objeto
fin

mover (x, y) = obj -> mover (obj, x, y)

así que cuando no proporcionas un objeto, hace una aplicación de función parcial
(devolviendo una función de 1 argumento) que luego podría usar dentro de un
normal |> cadena.

-
Responda a este correo electrónico directamente o véalo en Gi
.

ssfrr ¡Me gusta tu forma de pensar! No conocía la composición de la función |> . Veo que recientemente ha habido una discusión similar [https://github.com/JuliaLang/julia/issues/4963].

kmsquire Me gusta la idea de extender la composición de la función actual para permitirle especificar parámetros en la función de llamada ex. some_obj |> move(4, 5) |> scale(10) |> display . El soporte nativo significaría un cierre menos, pero lo que ssfrr sugirió es una forma viable por ahora y, como beneficio adicional, también debería ser compatible con la funcionalidad de composición de funciones extendida si se implementa.

Gracias por la pronta respuesta :)

En realidad, @ssfrr tenía razón: no es posible implementar esto como una función simple.

Lo que quieres son macros de subprocesamiento (por ejemplo, http://clojuredocs.org/clojure_core/clojure.core/-%3E). Desafortunadamente, @ -> @ - >> @ -? >> no es una sintaxis viable en Julia.

Sí, estaba pensando que las macros infijas serían una forma de implementar esto. No estoy lo suficientemente familiarizado con las macros para saber cuáles son las limitaciones.

Creo que esto funciona para la macro de @ssfrr :

Editar: esto podría ser un poco más claro:

import Base.Meta.isexpr
_ispossiblefn(x) = isa(x, Symbol) || isexpr(x, :call)

function _compose(x)
    if !isa(x, Expr)
        x
    elseif isexpr(x, :call) &&    #
        x.args[1] == :(|>) &&     # check for `expr |> fn`
        length(x.args) == 3 &&    # ==> (|>)(expr, fn)
        _ispossiblefn(x.args[3])  #

        f = _compose(x.args[3])
        arg = _compose(x.args[2])
        if isa(f, Symbol)
            Expr(:call, f, arg) 
        else
            insert!(f.args, 2, arg)
            f
        end
    else
        Expr(x.head, [_compose(y) for y in x.args]...)
    end
end

macro compose(x)
    _compose(x)
end
julia> macroexpand(:(<strong i="11">@compose</strong> x |> f |> g(1) |> h('a',"B",d |> c(fred |> names))))
:(h(g(f(x),1),'a',"B",c(d,names(fred))))

Si vamos a tener esta sintaxis |> , ciertamente estaría dispuesto a hacerla más útil de lo que es ahora. Usar solo para permitir que la función se aplique a la derecha en lugar de a la izquierda siempre ha parecido un desperdicio colosal de sintaxis.

+1. Es especialmente importante cuando utiliza Julia para el análisis de datos, donde normalmente tiene canales de transformación de datos. En particular, Pandas en Python es conveniente de usar porque puede escribir cosas como df.groupby ("algo"). Aggregate (sum) .std (). Reset_index (), que es una pesadilla para escribir con la sintaxis |> actual .

: +1: para esto.

(Ya había pensado en sugerir el uso del operador .. infix para esto ( obj..move(4,5)..scale(10)..display ), pero el operador |> también será bueno)

Otra posibilidad es agregar azúcar sintáctica para curry, como
f(a,~,b) traduciendo a x->f(a,x,b) . Entonces |> podría mantener su significado actual.

Oooh, esa sería una buena manera de convertir cualquier expresión en una función.

Posiblemente algo así como los literales de funciones anónimas de Clojure, donde #(% + 5) es la abreviatura de x -> x + 5 . Esto también se generaliza a varios argumentos con% 1,% 2, etc., por lo que #(myfunc(2, %1, 5, %2) es la abreviatura de x, y -> myfunc(2, x, 5, y)

Estéticamente, no creo que la sintaxis encaje muy bien en julia, que de otro modo sería muy legible, pero me gusta la idea general.

Para usar mi ejemplo anterior (y cambiar a la tilde de @malmaud en lugar de%), podría hacer

some_obj |> move(~, 4, 5) |> scale(~, 10) |> display

que se ve bastante bien.

Esto es bueno porque no le da al primer argumento ningún tratamiento especial. La desventaja es que de esta forma estamos tomando un símbolo.

Quizás este sea otro lugar donde podría usar una macro, por lo que la sustitución solo ocurre dentro del contexto de la macro.

Obviamente, no podemos hacer esto con ~ ya que esa ya es una función estándar en Julia. Scala hace esto con _ , lo que también podríamos hacer, pero hay un problema importante para averiguar qué parte de la expresión es la función anónima. Por ejemplo:

map(f(_,a), v)

Cual significa esto?

map(f(x->x,a), v)
map(x->f(x,a), v)
x->map(f(x,a), v)

Todas son interpretaciones válidas. Creo recordar que Scala usa las firmas de tipos de funciones para determinar esto, lo que me parece desafortunado, ya que significa que realmente no se puede analizar Scala sin conocer los tipos de todo. No queremos hacer eso (y no podríamos incluso si quisiéramos), por lo que tiene que haber una regla puramente sintáctica para determinar qué significado se pretende.

Bien, veo su punto sobre la ambigüedad de hasta dónde llegar. En Clojure, toda la expresión está envuelta en #(...) por lo que no es ambigua.

En Julia, ¿es idiomático usar _ como valor de indiferencia? Como x, _ = somfunc() si somefunc devuelve dos valores y solo desea el primero?

Para resolver eso, creo que necesitaríamos una macro con un uso similar a la interpolación:

some_obj |> @$(move($, 4, 5)) |> @$(scale($, 10)) |> display

pero, de nuevo, creo que se está volviendo bastante ruidoso en ese punto, y no creo que @$(move($, 4, 5)) nos dé nada sobre la sintaxis existente x -> move(x, 4, 5) , que en mi opinión es más bonita y más explícita.

Creo que esta sería una buena aplicación de una macro infija. Al igual que con # 4498, si cualquier regla define funciones como infijas aplicadas a macros, podríamos tener una macro @-> o @|> que tendría el comportamiento de subprocesamiento.

Sí, me gusta la idea de las macros infijas, aunque podría introducirse un nuevo operador para este uso en lugar de tener un sistema completo para macros in situ. Por ejemplo,
some_obj ||> move($,4,5) ||> scale($, 10) |> disp
o tal vez solo mantenga |> pero tenga una regla que
x |> f transforma implícitamente en x |> f($) :
some_obj |> scale($,10) |> disp

Amigos, todo realmente se ve feo: |> ||> etc.
Hasta ahora descubrí que la sintaxis de Julia es tan clara que estas cosas discutidas anteriormente no se ven tan bonitas en comparación con cualquier otra cosa.

En Scala es probablemente lo peor - tienen tantos operadores como ::,:, <<, >> + :: y así sucesivamente - hace que cualquier código sea feo y no legible para alguien sin unos meses de experiencia en el uso el idioma.

Siento que no te gusten las propuestas, Anton. Sería útil que hiciera una propuesta alternativa.

Oh, lo siento, no estoy tratando de ser cruel. Y sí, críticos sin propuestas
son inútiles.

Desafortunadamente, no soy un científico que construye lenguajes, así que simplemente no
saber qué proponer ... bueno, excepto hacer métodos opcionalmente propiedad de
objetos como en algunos idiomas.

Me gusta la frase "científico que construye lenguajes", suena mucho más grandiosa que los programadores numéricos hartos de Matlab.

Siento que casi todos los lenguajes tienen una forma de encadenar funciones, ya sea mediante la aplicación repetida de . en lenguajes OO, o una sintaxis especial solo para ese propósito en lenguajes más funcionales (Haskell, Scala, Mathematica, etc.). Esos últimos lenguajes también tienen una sintaxis especial para argumentos de funciones anónimas, pero no creo que Julia vaya realmente a eso.

Reiteraré el apoyo a la propuesta de Spencer: x |> f(a) se traducen a f(x, a) , de manera muy análoga a cómo funcionan los bloques do (y refuerza un tema común que el primer argumento de un La función es privilegiada en Julia para propósitos sintácticos de azúcar). x |> f se ve entonces como abreviatura de x |> f() . Es simple, no introduce ningún operador nuevo, maneja la gran mayoría de los casos para los que queremos el encadenamiento de funciones, es compatible con versiones anteriores y se ajusta a los principios de diseño de Julia existentes.

También creo que es la mejor propuesta aquí, el problema principal es que parece excluir la definición de |> para cosas como la redirección de E / S u otros fines personalizados.

Solo para tener en cuenta, . no es una sintaxis de encadenamiento de funciones especiales, pero sucede que funciona de esa manera si la función de la izquierda devuelve el objeto que acaba de modificar, que es algo que el desarrollador de la biblioteca tiene que hacer intencionalmente.

De manera análoga, en Julia, un desarrollador de biblioteca ya puede admitir el encadenamiento con |> definiendo sus funciones de N argumentos para devolver una función de 1 argumento cuando se le dan N-1 argumentos, como se menciona aquí

Sin embargo, eso parecería causar problemas si _desea_ que su función admita un número variable de argumentos, por lo que sería bueno tener un operador que pudiera realizar el relleno de argumentos.

@JeffBezanson , parece que este operador podría implementarse si hubiera una forma de hacer macros infijas. ¿Sabes si hay un problema ideológico con eso o simplemente no se implementa?

Recientemente, ~ fue en mayúsculas especiales para que citara sus argumentos y llamadas
la macro @~ por defecto. |> podría hacerse para hacer lo mismo.

Por supuesto, en unos meses, alguien pedirá <| para hacer lo mismo ...

El jueves 6 de febrero de 2014, Spencer Russell [email protected]
escribió:

Solo para notar. no es una sintaxis de encadenamiento de funciones especiales, pero sucede
para que funcione de esa manera si la función de la izquierda devuelve el objeto
modificado, que es algo que el desarrollador de la biblioteca tiene que hacer
intencionalmente.

De manera análoga, en Julia un desarrollador de biblioteca ya puede admitir el encadenamiento
con |> definiendo sus funciones de N argumentos para devolver una función
de 1 argumento cuando se dan argumentos N-1, como se menciona aquí https://github.com/JuliaLang/julia/issues/5571#issuecomment -33408448

Eso parecería causar problemas si _desea_ que su función admita
número variable de argumentos, sin embargo, tener un operador que pueda realizar
el relleno del argumento estaría bien.

@JeffBezanson https://github.com/JeffBezanson , parece que esto
El operador podría implementarse si hubiera una forma de hacer macros infijas. Vos si
¿Sabes si hay un problema ideológico con eso, o simplemente no se implementa?

-
Responda a este correo electrónico directamente o véalo en Gi
.

Bien, definitivamente no me gustaría que este fuera un caso especial. Manejarlo en el diseño de su API no es tan malo, e incluso la limitación de argumentos variables no es un gran problema si tiene anotaciones de tipo para eliminar la ambigüedad.

function move(obj::MyType, x, y, args...)
    # do stuff
    obj
end

move(args...) = obj::MyType -> move(obj, args...)

Creo que este comportamiento podría ser manejado por una macro @composable que manejaría la segunda declaración.

La idea de la macro infijo es atractiva para mí en la situación en la que se unificaría con la declaración de funciones infijas, que se analiza en # 4498.

¿Por qué los creadores de Julia están tan en contra de permitir que los objetos contengan sus propios métodos? ¿Dónde puedo leer más sobre esa decisión? ¿Qué pensamientos y teorías están detrás de esa decisión?

@meglio un lugar más útil para preguntas generales es la lista de correo o la etiqueta StackOverflow julia-lang . Ver la charla de Stefan y los archivos de los usuarios y dev listas para discusiones previas sobre este tema.

Para mí, lo más intuitivo es que un marcador de posición sea reemplazado por el
valor de la expresión anterior en la secuencia de cosas que está intentando componer, similar a la macro as-> clojure. Así que esto:

<strong i="8">@as</strong> _ begin
    3+3
    f(_,y)
    g(_) * h(_,z)
end

se ampliaría a:

g(f(3+3,y)) * h(f(3+3,y),z)

Puede pensar en la expresión de la línea anterior "bajando" para llenar el agujero de subrayado en la línea siguiente.

Comencé a esbozar algo diminuto como este último trimestre en una serie de dilación de la semana de finales.

También podríamos admitir una versión oneliner usando |> :

<strong i="19">@as</strong> _ 3+3 |> f(_,y) |> g(_) * h(_,z)

@porterjamesj , ¡me gusta esa idea!

Estoy de acuerdo; eso es bastante agradable y tiene una generalidad atractiva.
El 7 de febrero de 2014 a las 15:19, "Kevin Squire" [email protected] escribió:

@porterjamesj https://github.com/porterjamesj , ¡me gusta esa idea!

Responda a este correo electrónico directamente o véalo en Gi
.

Me gusta la idea de @porterjamesj no solo porque es un soplo de aire fresco, sino porque parece mucho más flexible que las ideas anteriores. No estamos casados ​​con usar solo el primer argumento, tenemos libertad de elección de la variable intermedia, y esto también parece algo que podemos implementar ahora mismo sin tener que agregar nueva sintaxis o casos especiales al lenguaje.

Tenga en cuenta que en Julia, debido a que no hacemos mucho del patrón obj.method(args...) , y en su lugar hacemos el patrón method(obj, args...) , tendemos a no tener métodos que devuelvan los objetos en los que operan para el expreso propósito del encadenamiento de métodos. (Que es lo que hace jQuery , y es fantástico en javascript). Así que no ahorramos tanto tipeo aquí, pero con el propósito de tener una configuración de "tuberías" entre funciones, creo que esto es realmente bueno.

Dado que -> y ->> clojure son solo casos especiales de lo anterior, y bastante comunes, probablemente también podríamos implementarlos con bastante facilidad. Aunque la cuestión de cómo llamarlos es un poco complicada. ¿Quizás @threadfirst y @threadlast ?

También me gusta la idea de que esto sea una macro.

¿No es mejor si la expansión, siguiendo el ejemplo, es algo como

tmp = 3+3; tmp = f(tmp); return h(tmp, z)

para evitar múltiples llamadas a la misma operación? (Quizás eso ya estaba implícito en la idea de @porterjamesj )

Otra sugerencia: ¿sería posible que la macro expanda los atajos f a f(_) y f(y) a f(_,y) ? Tal vez sea demasiado, pero creo que entonces tenemos una opción para usar el marcador de posición solo cuando sea necesario ... (los atajos, sin embargo, deben permitirse solo en llamadas a funciones solas, no en expresiones como g(_) * h(_,z) arriba)

@cdsousa, el punto sobre evitar múltiples llamadas es bueno. La implementación de clojure usa enlaces secuenciales let para lograr esto; Sin embargo, no estoy seguro de poder salirnos con la nuestra porque no sé lo suficiente sobre el rendimiento de nuestro let .

Entonces, ¿la macro @as usa saltos de línea y => como puntos de división para decidir cuál es la expresión de sustitución y qué se sustituye?

let rendimiento es bueno; ahora puede ser tan rápido como una asignación de variable cuando sea posible, y también bastante rápido en caso contrario.

@ssfrr en mi implementación de juguete solo filtra todos los nodos relacionados con el salto de línea que inserta el analizador (NB, realmente no entiendo todos estos, probablemente sería bueno tener documentación sobre ellos en el manual) y luego reduce la sustitución sobre la lista de expresiones que queda. Aunque creo que usar let sería mejor.

@cdsousa :

Otra sugerencia: ¿sería posible que la macro expanda los atajos f a f(_) y f(y) a f(_,y)

f a f(_) tiene sentido para mí. Para el segundo, soy de la opinión de que especificar explícitamente la ubicación es mejor, ya que personas razonables podrían argumentar que f(_,y) o f(y,_) es más natural.

Dado que -> y ->> clojure son solo casos especiales de lo anterior, y bastante comunes, probablemente también podríamos implementarlos con bastante facilidad. Aunque la cuestión de cómo llamarlos es un poco complicada. ¿Quizás @threadfirst y @threadlast ?

Creo que especificar la ubicación explícitamente con f(_,y...) o f(y..., _) permite que el código sea bastante comprensible. Si bien la sintaxis (y los operadores) adicionales tienen sentido en Clojure, realmente no tenemos operadores adicionales disponibles, y creo que las macros adicionales generalmente harían que el código sea menos claro.

Entonces, ¿la macro @as usa saltos de línea y => como puntos de división para decidir cuál es la expresión de sustitución y qué se sustituye?

Creo que es más natural usar |> como punto de división, ya que ya se usa para la canalización

Para que lo sepas, hay una implementación de la macro de subprocesos en Lazy.jl , que te permite escribir, por ejemplo:

@>> range() map(x->x^2) filter(iseven)

En el lado positivo, no requiere ningún cambio de idioma, pero se vuelve un poco feo si desea usar más de una línea.

También podría implementar @as> en Lazy.jl si hay interés. Lazy.jl ahora también tiene una macro @as .

También puede hacer algo como esto (aunque usando una sintaxis similar a Haskell) con Monads.jl (nota: debe actualizarse para usar la sintaxis actual de Julia). Pero sospecho que una versión especializada solo para el subproceso de argumentos debería poder evitar las dificultades de rendimiento que tiene el enfoque general.

Lazy.jl parece un paquete muy agradable y se mantiene activamente. ¿Hay alguna razón de peso para que esto deba estar en Base?

¿Cómo funcionará el encadenamiento de funciones con funciones que devuelven múltiples valores?
¿Cuál sería el resultado del encadenamiento, por ejemplo:

function foo(a,b)
    a+b, a*b   # x,y respectively
end

y bar(x,z,y) = x * z - y be?

¿No requeriría una sintaxis como bar(_1,z,_2) ?

Lanzando otro ejemplo:

data = [2.255, 3.755, 6.888, 7.999, 9.001]

La forma limpia de escribir: log(sum(round(data))) es data|>round|>sum|>log
Pero si quisiéramos hacer un registro en base 2 y quisiéramos redondear a 3 decimales,
entonces: solo podemos usar la primera forma:
log(2,sum(round(data,3)))

Pero idealmente nos gustaría poder hacer:
data|>round(_,3)|>sum|>log(2,_)
(o similar)

He hecho un prototipo de cómo sugiero que funcione.
https://github.com/oxinabox/Pipe.jl

No resuelve el punto de @gregid , pero estoy trabajando en eso ahora.
Tampoco maneja la necesidad de ampliar los argumentos.

Es similar a las macros de subprocesamiento Lazy.jl de @ one-more-minute, pero mantiene el símbolo |> para facilitar la lectura (preferencia personal).

Lo convertiré lentamente en un paquete, tal vez, en algún momento

Una opción más es:

data |>   x -> round(x,2)  |> sum |>  x -> log(2,x)

Aunque es más larga que log(2,sum(round(data,2))) esta notación a veces ayuda a la legibilidad.

@shashi eso no está mal, no pensé en eso,
Creo que, en general, es demasiado detallado para ser fácilmente legible.

https://github.com/oxinabox/Pipe.jl Ahora resuelve el problema de @gregid .
Aunque si solicita tanto _[1] como _[2] lo hace haciendo varias llamadas a la sustitución
Cuál no estoy seguro es el comportamiento más deseable.

Como forastero, creo que el operador de la tubería se beneficiaría de adaptar el tratamiento que le da F #.
Por supuesto, F # tiene curry, pero quizás se podría hacer algo de magia en la parte posterior para que no lo requiera. Como, en la implementación del operador, y no en el lenguaje central.

Esto haría que [1:10] |> map(e -> e^2) resulte en [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] .

Mirando hacia atrás, @ssfrr aludió a esto, pero el argumento obj en su ejemplo se daría automáticamente a map como el segundo argumento en mi ejemplo, evitando así a los programadores tener que definir sus funciones para apoyarlo.

¿Qué propones que significa?

El 5 de junio de 2015, a las 5:22 p.m., H-225 [email protected] escribió:

Como forastero, creo que una de las mejores formas de hacer esto sería adaptar el tratamiento de F #.
Por supuesto, F # tiene curry, pero quizás se podría hacer algo de magia en la parte posterior para que no lo requiera. Como, en la implementación del operador, y no en el lenguaje central.

Esto haría que [1:10] |> map (e -> e ^ 2) resulte en [1, 4, 9, 16, 25, 36, 49, 64, 81, 100].

Personalmente, creo que es agradable y claro sin ser demasiado detallado.

Obviamente, uno podría escribir result = map (sqr, [1:10]), pero ¿por qué tienen el operador de canalización?
¿Quizás hay algo que me estoy perdiendo?

-
Responda a este correo electrónico directamente o véalo en GitHub.

@StefanKarpinski
Básicamente, haga que el operador trabaje como:

  • x |> y(f) = y(x, f)
  • x |> y(f) = y(f, x)

Quizás tenga un patrón de interfaz en el que cualquier función que se use con el operador tome los datos para operar como el primer o último argumento, dependiendo de cuál de los anteriores se seleccione para ser ese patrón.
Entonces, para la función map como ejemplo, map sería map(func, data) o map(data, func) .

¿Está eso más claro?

Lazy.jl parece un paquete muy agradable y se mantiene activamente. ¿Hay alguna razón de peso para que esto deba estar en Base?

Creo que esta es la pregunta importante aquí.

La razón por la que esto puede ser deseable en base es 2 veces:

1.) Es posible que deseemos fomentar la canalización como si fuera el estilo juliano; se pueden argumentar que es más legible
2.) cosas como Lazy.jl, FunctionalData.jl y mi propio Pipe.jl requieren una macro para envolver la expresión sobre la que actuará, lo que la hace menos legible.

Siento que la respuesta puede estar en tener Infix Macros.
Y definiendo |> como tal.

No estoy seguro de que tener |>, (o su primo el bloque do) pertenezca al núcleo en absoluto.
Pero las herramientas no existen para definirlas fuera del analizador.

La capacidad de tener ese tipo de sintaxis de canalización parece muy agradable. ¿Podría agregarse eso a Base, es decir, x |> y(f) = y(f, x) part, que Lazy.j, FunctionalData.jl y Pipe.jl podrían usar? : +1:

Habiendo examinado el código que usa las diversas implementaciones de esto en paquetes, personalmente lo encuentro ilegible y muy poco juliano. El juego de palabras de izquierda a derecha no ayuda a la legibilidad, solo hace que su código se destaque como al revés del resto del código perfectamente normal que usa paréntesis para la evaluación de funciones. Prefiero desalentar una sintaxis que lleve a 2 estilos diferentes donde el código escrito en cualquiera de los estilos se ve al revés y al revés en relación con el código escrito en el otro. ¿Por qué no conformarse con la sintaxis perfectamente buena que ya tenemos y alentar a que las cosas se vean más uniformes?

@tkelman
Personalmente, lo veo desde un punto de vista un tanto utilitario.
Por supuesto, tal vez si está haciendo algo simple, entonces no es necesario, pero si está escribiendo una función, diga, eso hace algo bastante complicado o largo aliento (fuera de mi cabeza: manipulación de datos, por ejemplo), entonces creo que ahí es donde brilla la sintaxis de la canalización.

Aunque entiendo lo que quieres decir; _sería_ más uniforme si tuviera una sintaxis de llamada de función para todo. Sin embargo, personalmente, creo que es mejor facilitar la escritura de código [complicado] que se pueda entender fácilmente. Por supuesto, tienes que aprender la sintaxis y lo que significa, pero, en mi humilde opinión, |> no es más difícil de entender que cómo llamar a una función.

@tkelman Lo vería desde un punto de vista diferente. Evidentemente, hay personas que prefieren ese estilo de programación. Puedo ver que tal vez desee tener un estilo coherente para el código fuente en Base, pero esto solo se trata de agregar el soporte del analizador para su estilo preferido de programación _sus_ aplicaciones Julia. ¿Los julianos realmente quieren intentar dictar o reprimir algo que otras personas encuentran beneficioso?
Descubrí que la canalización de cosas juntas es muy útil en Unix, así que aunque nunca he usado un lenguaje de programación que lo habilite en el lenguaje, al menos le daría el beneficio de la duda.

Tenemos |> como operador de canalización de funciones, pero existen limitaciones de implementación sobre cómo se hace actualmente que lo hacen bastante lento en este momento.

La canalización es genial en un shell de Unix donde todo toma texto dentro y fuera. Con tipos más complicados y múltiples entradas y salidas, no es tan claro. Entonces tenemos dos sintaxis, pero una tiene mucho menos sentido en el caso MIMO. El soporte del analizador para estilos alternativos de programación o DSL no suele ser necesario, ya que tenemos macros potentes.

OK, gracias, iba por el comentario de @oxinabox :

Pero las herramientas no existen para definirlas fuera del analizador.

¿Se entiende qué se haría para eliminar las limitaciones de implementación a las que se refirió?

Algunas de las sugerencias anteriores podrían implementarse potencialmente haciendo que |> analice sus argumentos como una macro en lugar de como una función. El antiguo significado de canalización de objetos de comando de |> ha quedado obsoleto, por lo que en realidad podría liberarse para hacer algo diferente con 0.5-dev.

Sin embargo, esta elección me recuerda bastante al análisis especial de ~ que creo que es un error por las razones que he mencionado en otra parte.

Analizar ~ es una locura, es una función en la base. Usar _ , _1 , _2 , parece _más_ razonable (especialmente si subes si estas variables están definidas en otra parte del alcance). Aún así, hasta que tengamos funciones anónimas más eficientes, esto parece que no va a funcionar ...

implementado haciendo |> analizar sus argumentos como una macro en lugar de como una función

¡A menos que hagas eso!

Analizar ~ es una locura, es una función en la base

Es un operador unario para la versión bit a bit. Infix binary ~ analiza como una macro, ref https://github.com/JuliaLang/julia/issues/4882 , que creo que es un uso extraño de un operador ascii (https://github.com/ JuliaLang / julia / pull / 11102 # issuecomment-98477891).

@tkelman

Entonces tenemos dos sintaxis, pero una tiene mucho menos sentido en el caso MIMO.

3 Sintaxis. Mas o menos.
Pipe in, llamada de función normal y Do-blocks.
Discutible incluso 4, ya que las macros también usan una convención diferente.


Para mi,
el Readorder (es decir, de izquierda a derecha) == El orden de la aplicación hace, para las cadenas de funciones SISO, mucho más claro.

Hago mucho código como (Usando iterators.jl y pipe.jl):

  • loaddata(filename) |> filter(s-> 2<=length(s)<=15, _) |> take!(150,_) |> map(eval_embedding, _)
  • results |> get_error_rate(desired_results, _) |> round(_,2)

Para SISO, es mejor (para mi preferencia personal), para MIMO no lo es.

Julia parece haberse decidido a que haya múltiples formas correctas de hacer las cosas.
Lo cual no estoy 100% seguro de que sea algo bueno.

Como dije, me gustaría que los bloques Pipe and Do salieran del idioma principal.

Los Do-blocks tienen bastantes casos de uso muy útiles, pero me ha molestado un poco que tengan que usar la primera entrada como función, no siempre encaja bien con la filosofía de envío múltiple (y tampoco pandas / UFCS estilo D con postfix data.map(f).sum() , sé que es popular pero no creo que se pueda combinar de manera efectiva con envío múltiple).

La canalización probablemente pueda quedar obsoleta muy pronto y dejarse en manos de paquetes para su uso en DSL como Pipe.jl.

Julia parece haberse decidido a que haya múltiples formas correctas de hacer las cosas.
Lo cual no estoy 100% seguro de que sea algo bueno.

Está relacionado con la cuestión de si podemos o no aplicar rigurosamente una guía de estilo para toda la comunidad. Hasta ahora no hemos hecho mucho aquí, pero para la interoperabilidad, consistencia y legibilidad de paquetes a largo plazo, creo que esto será cada vez más importante a medida que crece la comunidad. Si eres la única persona que alguna vez leerá tu código, vuélvete loco y haz lo que quieras. Sin embargo, si no es así, vale la pena intercambiar una legibilidad ligeramente peor (en su propia opinión) en aras de la uniformidad.

@tkelman @oxinabox
Todavía tengo que encontrar una razón clara por la que no debería incluirse en el lenguaje, o de hecho en los paquetes "centrales". [por ejemplo: Base]
Personalmente, creo que hacer una macro de |> podría ser la respuesta.
¿Algo como esto quizás? (¡No soy un programador maestro de Julia!)

macro (|>) (x, y::Union(Symbol, Expr))
    if isa(y, Symbol)
        y = Expr(:call, y) # assumes y is callable
    end
    push!(y.args, x)
    return eval(y)
end

Con Julia v0.3.9, no pude definirlo dos veces: una con un símbolo y otra con una expresión; mi comprensión [limitada] de Union es que hay un impacto en el rendimiento al usarlo, así que supongo que sería algo para rectificar en mi código de ejemplo de juguete.

Por supuesto, existe un problema con la sintaxis de uso para esto.
Por ejemplo, para ejecutar el equivalente de log(2, 10) , debe escribir @|> 10 log(2) , lo cual no es deseable aquí.
Tengo entendido que tendrías que poder marcar de alguna manera funciones / macros como "infijables", por así decirlo, de modo que puedas escribirlo así: 10 |> log(2) . (¡Corrija si está mal!)
Ejemplo artificial, lo sé. ¡No puedo pensar en uno bueno ahora mismo! =)

También vale la pena señalar un área que no he cubierto en mi ejemplo ...
Entonces, por ejemplo:

julia> for e in ([1:10], [11:20] |> zip) println(e) end
(1,11)
(2,12)
(3,13)
(4,14)
(5,15)
(6,16)
(7,17)
(8,18)
(9,19)
(10,20)

Una vez más, un ejemplo artificial, ¡pero espero que entiendas el punto!
Hice algunos toques, pero en el momento de escribir esto no pude entender cómo implementar eso, yo mismo.

El 9 de junio de 2015, a las 9:37 p.m., H-225 [email protected] escribió:

Todavía tengo que encontrar una razón clara por la que no debería incluirse en el idioma

Esta es la postura mental incorrecta para el diseño de lenguajes de programación. La pregunta debe ser "¿por qué?" en lugar de "¿por qué no?" Cada función necesita una razón convincente para su inclusión, e incluso con una buena razón, debe pensarlo detenidamente antes de agregar algo. ¿Puedes vivir sin eso? ¿Existe una forma diferente de lograr lo mismo? ¿Existe una variación diferente de la característica que sería mejor y más general o más ortogonal a las características existentes? No estoy diciendo que esta idea en particular no pueda suceder, pero debe haber una justificación mucho mejor que "¿por qué no?" con algunos ejemplos que no son mejores que la sintaxis normal.

La pregunta debe ser "¿por qué?" en lugar de "¿por qué no?"

+ 1_000_000

En efecto.
Vea esta publicación de blog bastante conocida:
Cada función comienza con -100 puntos.
Necesita hacer una gran mejora para que valga la pena agregarlo al lenguaje.

FWIW, Pyret (http://www.pyret.org/) pasó por esta discusión exacta hace unos meses. El lenguaje admite una notación de "bala de cañón" que originalmente funcionaba de la manera que la gente propone con |> . En Pyret,

[list: 1, 2, 3, 5] ^ map(add-one) ^ filter(is-prime) ^ sum() ^ ...

Entonces, la notación de bala de cañón desaconsejó agregar argumentos a las funciones.

No pasó mucho tiempo antes de que decidieran que esta sintaxis era demasiado confusa. ¿Por qué se llama a sum() sin ningún argumento? etc. Al final, optaron por una elegante alternativa de curry:

[list: 1, 2, 3, 5] ^ map(_, add-one) ^ filter(_, is-prime) ^ sum() ^ ...

Esto tiene la ventaja de ser más explícito y simplifica el operador ^ a una función simple.

Sí, eso me parece mucho más razonable. También es más flexible que el curry.

@StefanKarpinski Estoy un poco confundido. ¿Quiso decir más flexible que encadenar (no curry)? Después de todo, la solución de Pyret fue simplemente usar el curry, que es más general que el encadenamiento.

Tal vez, si modificamos un poco la sintaxis |> (realmente no sé qué tan difícil es implementar, tal vez entre en conflicto con | y > ), podría establecer algo flexible y legible.

Definiendo algo como

foo(x,y) = (y,x)
bar(x,y) = x*y

Tendríamos:

randint(10) |_> log(_,2) |> sum 
(1,2) |_,x>  foo(_,x)   |x,_>   bar(_,2) |_> round(_, 2) |> sum |_> log(_, 2)

En otras palabras, tendríamos un operador como |a,b,c,d> donde a , b , c y d obtendrían los valores devueltos de la última expresión (en orden) y úsela en marcadores de posición dentro de la siguiente.

Si no hay variables dentro de |> , funcionaría como funciona ahora. También podríamos establecer un nuevo estándar: f(x) |> g(_, 1) obtendría todos los valores devueltos por f(x) y los asociaría con el marcador _ posición

@samuela , lo que quise decir es que con currying solo puedes omitir los argumentos finales, mientras que con el enfoque _ , puedes omitir cualquier argumento y obtener una función anónima. Es decir, dado f(x,y) con curry, puedes hacer f(x) para obtener una función que haga y -> f(x,y) , pero con guiones bajos puedes hacer f(x,_) por lo mismo pero también haz f(_,y) para obtener x -> f(x,y) .

Si bien me gusta la sintaxis de subrayado, todavía no estoy satisfecho con ninguna respuesta propuesta a la pregunta de cuánto de la expresión circundante "captura".

¿Qué haces si una función devuelve varios resultados? ¿Tendría que pasar una tupla a la posición _? ¿O podría haber una sintaxis para dividirlo sobre la marcha? Puede ser una pregunta estúpida, si es así, ¡perdón!

@StefanKarpinski Ah, ya veo lo que quieres decir. Convenido.

@ScottPJones, la respuesta obvia es permitir flechas artísticas ASCII:
http://scrambledeggsontoast.github.io/2014/09/28/needle-announce/

@simonbyrne ¡ Eso se ve incluso peor que programar en Fortran IV en tarjetas perforadas, como lo hice en mi juventud malgastada! Me preguntaba si alguna sintaxis como _1, _2, etc. podría permitir separar un retorno múltiple, ¿o es solo una idea estúpida de mi parte?

@simonbyrne Eso es brillante. Implementar eso como una macro de cadena sería un proyecto GSoC increíble.

¿Por qué se llama a sum () sin ningún argumento?

Creo que el argumento implícito también es una de las cosas más confusas sobre la notación do , por lo que sería bueno si pudiéramos utilizar la misma convención para eso también (aunque me doy cuenta de que es mucho más difícil, como ya está horneado en el idioma).

@simonbyrne ¿No crees que se pueda hacer de forma inequívoca? Si es así, creo que vale la pena romper (la notación actual do ), si se puede hacer más lógica, más general y coherente con el encadenamiento.

@simonbyrne Sí, estoy totalmente de acuerdo. Entiendo la motivación de la notación actual do , pero creo firmemente que no justifica la gimnasia sintáctica.

@samuela con respecto al mapa (f, _) vs solo mapa (f). Estoy de acuerdo en que algo de magia desugaring sería confuso, pero creo que map (f) es algo que debería existir. No requeriría y el azúcar solo agregaría un método simple para mapear.
p.ej

map(f::Base.Callable) = function(x::Any...) map(f,x...) end

es decir, map toma una función y luego devuelve una función que trabaja en cosas que son iterables (más o menos).

De manera más general, creo que deberíamos inclinarnos hacia funciones que tengan métodos de "conveniencia" adicionales, en lugar de algún tipo de convención de que |> siempre mapea datos al primer argumento (o similar).

En la misma línea, podría haber un

type Underscore end
_ = Underscore()

y una convención general de que las funciones deberían / ​​podrían tener métodos que toman guiones bajos en ciertos argumentos y luego devuelven funciones que toman menos argumentos. Estoy menos convencido de que esto sería una buena idea, ya que sería necesario agregar 2 ^ n métodos para cada función que toma n argumentos. Pero es un enfoque. Me pregunto si sería posible no tener que agregar explícitamente tantos métodos, sino enganchar la búsqueda de métodos, de modo que si algún argumento es de tipo subrayado, se devuelva la función adecuada.

De todos modos, definitivamente creo que tener una versión de mapa y filtro que solo tome un invocable y devuelva un invocable tiene sentido, la cosa con el subrayado puede o no ser viable.

@patrickthebold
Me imagino que x |> map(f, _) => x |> map(f, Underscore()) => x |> map(f, x) , como propones, sería la forma más sencilla de implementar map(f, _) , ¿verdad? - ¿Solo tienes que _ sea ​​una entidad especial para la que programarías?

Sin embargo, no estoy seguro de si eso sería mejor que que Julia lo infiera automáticamente, presumiblemente usando la sintaxis |> lugar de tener que programarlo usted mismo.

Además, con respecto a su propuesta de map , me gusta un poco. De hecho, para el actual |> eso sería bastante útil. Sin embargo, me imagino que sería más simple mejor simplemente implementar inferencia automática de x |> map(f, _) => x |> map(f, x) en su lugar?

@StefanKarpinski Tiene sentido. No lo había pensado de esa manera.

Nada de lo que dije estaría vinculado a |> de ninguna manera. Lo que quise decir con respecto a _ sería, por ejemplo, agregar métodos a < como tal:

<(_::Underscore, x) = function(z) z < x end
<(x, _::Underscore) = function(z) x < z end

Pero nuevamente, creo que esto sería un fastidio a menos que hubiera una manera de agregar automáticamente los métodos apropiados.

Una vez más, lo que tienen los guiones bajos es independiente de agregar el método de conveniencia al mapa como se describe anteriormente. Creo que ambos deberían existir, de una forma u otra.

@patrickthebold Un enfoque de este tipo con un tipo definido por el usuario para subrayado, etc. supondría una carga significativa e innecesaria para el programador al implementar funciones. Tener que enumerar los 2 ^ n de

f(_, x, y) = ...
f(x, _, y) = ...
f(_, _, y) = ...
...

sería muy molesto, por no mencionar poco elegante.

Además, su propuesta con map supongo que proporcionaría una sintaxis alternativa para map(f) con funciones básicas como map y filter pero en general sufre lo mismo problema de complejidad como el enfoque de subrayado manual. Por ejemplo, por func_that_has_a_lot_of_args(a, b, c, d, e) tendrías que pasar por el agotador proceso de escribir cada posible "curry"

func_that_has_a_lot_of_args(a, b, c, d, e) = ...
func_that_has_a_lot_of_args(b, c, d, e) = ...
func_that_has_a_lot_of_args(a, b, e) = ...
func_that_has_a_lot_of_args(b, d, e) = ...
func_that_has_a_lot_of_args(a, d) = ...
...

E incluso si lo hiciera, aún se enfrentaría a una cantidad absurda de ambigüedad al llamar a la función: ¿ func_that_has_a_lot_of_args(x, y, z) refiere a la definición donde x=a,y=b,z=c o x=b,y=d,z=e , etc. ? Julia discerniría entre ellos con información de tipo de tiempo de ejecución, pero para el programador lego que lee el código fuente, no estaría totalmente claro.

Creo que la mejor manera de hacer bien el curry de subrayado es simplemente incorporarlo al idioma. Después de todo, sería un cambio muy sencillo en el compilador. Siempre que aparezca un guión bajo en una aplicación de función, simplemente sáquelo para crear un lambda. Comencé a buscar implementar esto hace unas semanas, pero desafortunadamente no creo que tenga suficiente tiempo libre en las próximas semanas para llevarlo a cabo. Para alguien familiarizado con el compilador de Julia, probablemente no tomaría más de una tarde hacer que todo funcione.

@samuela
¿Puede aclarar lo que quiere decir con "extraerlo para crear una lambda"? - Soy curioso. Yo también me he preguntado cómo se puede implementar.

@patrickthebold
Ah, ya veo. Es de suponer que podrías usar algo como esto: filter(_ < 5, [1:10]) => [1:4] ?
Personalmente, encontraría filter(e -> e < 5, [1:10]) más fácil de leer; más coherente, con un significado menos oculto, aunque te lo concedo, es más conciso.

¿A menos que tenga un ejemplo donde realmente brille?

@samuela

Además, su propuesta con map supongo que proporcionaría una sintaxis alternativa para map (f) con funciones básicas como mapa y filtro, pero en general sufre el mismo problema de complejidad que el enfoque de subrayado manual.

No estaba sugiriendo que esto se hiciera en general, solo por map y filter , y posiblemente en algunos otros lugares donde parece obvio. Para mí, así es como debería funcionar map : tomar una función y devolver una función. (Estoy bastante seguro de que eso es lo que hace Haskell).

sería muy molesto, por no mencionar poco elegante.

Creo que estamos de acuerdo en eso. Espero que haya una forma de agregar algo al lenguaje para manejar las invocaciones de métodos donde algunos argumentos son de tipo subrayado. Pensándolo bien, creo que se reduce a tener un carácter especial que se expande automáticamente en una lambda, o tener un tipo especial que se expande automáticamente en una lambda. No me siento fuerte de ninguna manera. Puedo ver ventajas y desventajas en ambos enfoques.

@ H-225 sí, el subrayado es solo una conveniencia sintáctica. No estoy seguro de cuán común es, pero Scala ciertamente lo tiene. Personalmente me gusta, pero creo que es solo una de esas cosas de estilo.

@ H-225 Bueno, en este caso creo que un ejemplo convincente y relevante sería el encadenamiento de funciones. En lugar de tener que escribir

[1, 2, 3, 5]
  |> x -> map(addone, x)
  |> x -> filter(isprime, x)
  |> sum
  |> x -> 3 * x
  |> ...

uno podría simplemente escribir

[1, 2, 3, 5]
  |> map(addone, _)
  |> filter(isprime, _)
  |> sum
  |> 3 * _
  |> ...

Me encuentro, sin saberlo, usando esta sintaxis de subrayado (o alguna variante leve) constantemente en idiomas que la admiten y solo me doy cuenta de lo útil que es cuando hago la transición para trabajar en idiomas que no la admiten.

Hasta donde yo sé, actualmente hay al menos 3.5 bibliotecas / enfoques que intentan abordar este problema en Julia: la función |> incorporada de Julia, Pipe.jl, Lazy.jl y 0.5 para la función do incorporada de Julia

@samuela, si quieres jugar con una implementación de esta idea, puedes probar FunctionalData.jl, donde tu ejemplo se vería así:

<strong i="7">@p</strong> map [1,2,3,4] addone | filter isprime | sum | times 3 _

La última parte muestra cómo canalizar la entrada al segundo parámetro (el valor predeterminado es el argumento uno, en cuyo caso se puede omitir _ ). ¡Comentarios muy apreciados!


Editar: lo anterior simplemente se reescribe a:

times(3, sum(filter(map([1,2,3,4],addone), isprime)))

que usa FunctionalData.map y filter en lugar de Base.map y filter. La principal diferencia es el orden de los argumentos, la segunda diferencia es la convención de indexación (ver documentos). En cualquier caso, Base.map puede usarse simplemente invirtiendo el orden de los argumentos. @p es una regla de reescritura bastante simple (de izquierda a derecha se convierte de interior a exterior, además de soporte para curry simple: <strong i="17">@p</strong> map data add 10 | showall convierte

showall(map(data, x->add(x,10)))

Hack puede introducir algo como esto: https://github.com/facebook/hhvm/issues/6455. Están usando $$ que está fuera de la mesa para Julia ( $ ya está demasiado sobrecargado).

FWIW, realmente me gusta la solución de Hack para esto.

También me gusta, mi principal reserva es que todavía me gustaría una notación lambda terser que podría usar _ para variables / ranuras y sería bueno asegurarme de que no entren en conflicto.

¿No se podría usar __ ? ¿En qué sintaxis lambda estás pensando? _ -> sqrt(_) ?

Claro que podríamos. Esa sintaxis ya funciona, se trata más de una sintaxis que no requiere la flecha, por lo que puede escribir algo como map(_ + 2, v) , el problema real es cuánto de la expresión circundante es _ pertenece a.

¿Mathematica no tiene un sistema similar para argumentos anónimos? Como hacer
manejan el alcance del límite de esos argumentos?
El martes 3 de noviembre de 2015 a las 9:09 a.m. Stefan Karpinski [email protected]
escribió:

Claro que podríamos. Esa sintaxis ya funciona, se trata más de una sintaxis que
no requiere la flecha, para que pueda escribir algo a lo largo de las líneas
del mapa (_ + 2, v), el problema real es cuánto de los alrededores
expresión a la que pertenece _.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/5571#issuecomment -153383422.

https://reference.wolfram.com/language/tutorial/PureFunctions.html , mostrando
el símbolo #, es en lo que estaba pensando.
El martes 3 de noviembre de 2015 a las 9:34 a.m., Jonathan Malmaud [email protected] escribió:

¿Mathematica no tiene un sistema similar para argumentos anónimos? Como hacer
manejan el alcance del límite de esos argumentos?
El martes 3 de noviembre de 2015 a las 9:09 a.m. Stefan Karpinski [email protected]
escribió:

Claro que podríamos. Esa sintaxis ya funciona, se trata más de una sintaxis que
no requiere la flecha, para que pueda escribir algo a lo largo de las líneas
del mapa (_ + 2, v), el problema real es cuánto de los alrededores
expresión a la que pertenece _.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/5571#issuecomment -153383422.

Mathematica usa & para delimitarlo.

En lugar de hacer algo tan general como una sintaxis lambda más corta (que podría tomar una expresión arbitraria y devolver una función anónima), podríamos solucionar el problema del delimitador limitando las expresiones aceptables a las llamadas a funciones y las variables / ranuras aceptables a parámetros completos. Esto nos daría una sintaxis de currying multiparámetro muy limpia al _ reemplaza parámetros completos, la sintaxis podría ser mínima, intuitiva y sin ambigüedades. map(_ + 2, _) se traduciría en x -> map(y -> y + 2, x) . La mayoría de las expresiones de llamada que no son de función que le gustaría lambdafy probablemente serían más largas y amigables para -> o do todos modos. Creo que la compensación entre usabilidad y generalidad valdría la pena.

@durcan , eso suena prometedor, ¿puedes desarrollar un poco la regla? ¿Por qué el primer _ permanece dentro del argumento de map mientras que el segundo consume toda la expresión map ? No tengo claro qué significa "limitar las expresiones aceptables a las llamadas de función", ni qué significa "limitar las variables / ranuras aceptables a parámetros completos" ...

Ok, creo que entiendo la regla, habiendo leído parte de la documentación de Dylan, pero tengo que preguntarme si map(_ + 2, v) funciona pero map(2*_ + 2, v) no funciona.

También está el asunto muy delicado de que esto significa que _ + 2 + _ significará (x,y) -> x + 2 + y mientras que _ ⊕ 2 ⊕ _ significará y -> (x -> x + 2) + y porque + y * son los únicos operadores que actualmente analizan como llamadas de función de múltiples argumentos en lugar de como operaciones asociativas por pares. Ahora bien, se podría argumentar que esta inconsistencia debería arreglarse, aunque eso parecería implicar que el _parser_ tenga una opinión sobre qué operadores son asociativos y cuáles no, lo que parece malo. Pero yo diría que cualquier esquema que requiera saber si el analizador analiza a + b + c como una llamada de función única o una llamada anidada puede ser algo cuestionable. ¿Quizás la notación infija debería manejarse especialmente? Pero no, eso también se siente sospechoso.

Sí, ha dado con el equilibrio. Por un lado, las cadenas largas de operadores es la sintaxis que tiene más problemas dadas algunas de nuestras opciones de análisis actuales (aunque es difícil criticar la semántica de una característica del lenguaje por depender de la semántica actual del lenguaje). Por otro lado, las largas cadenas de llamadas a funciones es donde sobresale. Por ejemplo, podríamos reescribir su problema como:

2*_ |> 2+_ |> map(_, v)

De todos modos, no creo que el problema del infijo deba interponerse en el camino de tener una opción libre de delimitadores limpia. Realmente ayudaría con la mayoría de las llamadas de función normales, que es una especie de problema en cuestión. Si lo desea, puede tener un delimitador opcional para ayudar a resolver esta ambigüedad particular (aquí estoy robando & para ese rol):

_ ⊕ 2 ⊕ _    # y -> (x -> x + 2) + y
_ ⊕ 2 ⊕ _ &  # (y , x) -> x + 2 + y

Esta es la mejor propuesta hasta ahora, pero no estoy del todo vendido. Está bastante claro lo que sucede cuando las llamadas a funciones son explícitas, pero menos claro cuando las llamadas a funciones están implícitas en la sintaxis infija.

Me gusta pensar en este enfoque como un currying más flexible y generalizado, en lugar de una lambda corta y dulce (e incluso entonces podemos llegar prácticamente hasta allí con un delimitador opcional). Me encantaría algo más perfecto, pero sin agregar más ruido simbólico (la antítesis de este tema) no estoy seguro de cómo llegar.

Sí, me gusta excepto por el infijo. Esa parte puede ser reparable.

Bueno, curry en posición infija podría ser un error de sintaxis:

map(+(*(2, _), 2), v)      # curry is OK syntax, but obviously not what you wanted
map(2*_ + 2, v)            # ERROR: syntax: infix curry requires delimitation
map(2*_ + 2 &, v)          # this means what we want
map(*(2,_) |> +(_,2), v)   # as would this

También podría ser una advertencia, supongo.

Llamar a esto curry me parece confuso y equivocado.

Claro, esto es más como una aplicación de función parcial (que opcionalmente se convierte en una lambda argumentada de forma anónima), supongo. Dejando de lado el nombre, ¿alguna idea?

Estoy pensando en algo como esto:

  • Si _ aparece solo como cualquiera de los argumentos de una expresión de llamada de función, esa llamada de función se reemplaza con una expresión de función anónima que toma tantos argumentos como argumentos tenga la función _ , cuyo cuerpo es el expresión original con _ argumentos reemplazados con argumentos lambda en orden.
  • Si _ aparece en otro lugar, la expresión circundante hasta, pero sin incluir el nivel de precedencia de la _ en esta expresión, cuyo cuerpo es la expresión original con _ instancias reemplazadas con argumentos lambda en orden.

Ejemplos:

  • f(_, b)x -> f(x, b)
  • f(a, _)x -> f(a, x)
  • f(_, _)(x, y) -> f(x, y)
  • 2_^2x -> 2x^2
  • 2_^_(x, y) -> 2x^y
  • map(_ + 2, v)map(x -> x + 2, v)
  • map(2_ + 2, v)map(x -> 2x + 2, v)
  • map(abs, _)x -> map(abs, x)
  • map(2_ + 2, _)x -> map(y -> 2y + 2, x)
  • map(2_ - _, v, w)map((x, y) -> 2x - y, v, w)
  • map(2_ - _, v, _)x -> map((y, z) -> 2y - z, v, x)
  • map(2_ - _, _, _)(x, y) -> map((z, w) -> 2z - w, x, y)
  • _x -> x
  • map(_, v)x -> map(x, v)
  • map((_), v)map(x -> x, v)
  • f = _f = x -> x
  • f = 2_f = x -> 2x
  • x -> x^_x -> y -> x^y
  • _ && _(x, y) -> x && y
  • !_ && _(x, y) -> !x && y

El único lugar en el que esto comienza a ponerse peligroso son los condicionales; esos ejemplos se vuelven un poco extraños.

Esto todavía es un poco complicado y sin principios y hay casos de esquina, pero estoy llegando a algún lado.

Esto me parece una muy mala idea, casi toda la sintaxis en Julia es bastante familiar si ha utilizado otros lenguajes de programación. Las personas que miran el azúcar de sintaxis de esta manera no tendrán idea de lo que significa el "beneficio" de salvar un par de caracteres.

Ejemplos que me dejan un poco menos feliz:

  • 2v[_]x -> 2v[x] (bueno)
  • 2f(_)2*(x -> f(x)) (no tan bueno)
  • _ ? "true" : "false"(x -> x) ? "true" : "false"
  • _ ? _ : 0(x -> x) ? (y -> y) : 0

Creo que algo más profundo está sucediendo aquí: hay una noción de posiciones sintácticas en las que un objeto de función _tiene sentido_ - y desea expandirse al más cercano de ellos. La posición clásica que "quiere" un objeto de función es un argumento para otra función, pero el lado derecho de una asignación es otro lugar del que se puede decir que "quiere" un objeto de función (o tal vez sea más exacto decir que preferiría convertir una función en el cuerpo de una función).

Quizás, pero se podría (y se hizo) el mismo argumento acerca de la sintaxis do-block, que creo que, en general, ha sido muy útil y exitosa. Esto está estrechamente relacionado con una mejor sintaxis para la vectorización. Tampoco es algo sin precedentes: Scala usa _ de manera similar, y Mathematica usa # manera similar.

Creo que también podría argumentar que la elección _ desconocida_ de
envío múltiple en lugar de un operador de punto de envío único esencialmente
obliga a la decisión de tener una sintaxis sucinta para los argumentos pronominales
recuperar el orden SVO con el que la gente está familiarizada.

El martes, 17 de noviembre de 2015 a las 12:09 p.m. Stefan Karpinski [email protected]
escribió:

Quizás, pero el mismo argumento podría (y se hizo) sobre el bloque de hacer
sintaxis, que creo que, en general, ha tenido mucho éxito y ha resultado útil.
Esto está estrechamente relacionado con una mejor sintaxis para la vectorización. Tampoco lo es
eso sin precedentes - Scala usa _ de manera similar, y Mathematica usa #
similar.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/5571#issuecomment -157437223.

Esto también existe en C ++ con múltiples soluciones de biblioteca en Boost en particular, que usan _1, _2, _3 como argumentos (por ejemplo, _1(x, y...) = x , _2(x, y, z...) = y etc.), con la limitación de poder llamar, por ejemplo, fun(_1) por x -> fun(x) , fun debe ser explícitamente compatible con la biblioteca (generalmente a través de una llamada macro, para que fun acepte un "tipo lambda "como parámetro).

Realmente me gustaría que esta tersa notación lambda esté disponible en Julia.
Con respecto al problema de 2f(_) desugaring a 2*(x -> f(x)) : ¿tendría sentido modificar las reglas en la línea de "si se aplica la primera regla, por ejemplo f(_) , entonces evaluar recursivamente las reglas con f(_) desempeñando el papel de _ . Esto también permitiría, por ejemplo, f(g(_))x -> f(g(x)) , con la "regla de paréntesis" que permite fácilmente deténgase en el nivel deseado, por ejemplo, f((g(_)))f(x->g(x)) .

Me gusta mucho el nombre "notación lambda tersa" para esto. (Mucho mejor que curry).

Realmente preferiría la claridad de _1 , _2 , _3 si pasa lambdas de varios argumentos. En general, a menudo encuentro que reutilizar nombres de variables en el mismo ámbito puede ser confuso ... y tener _ siendo xey en _la misma expresión_ parece una locura.

Encontré que esta misma concisa scala _ -syntax ha causado un poco de confusión (vea todos los usos de _ en scala ).

Además, a menudo quieres hacer:

x -> f(x) + g(x)

o similar, y creo que me sorprendería si lo siguiente no funcionara:

f(_) + g(_)

También es posible que desee cambiar el orden de los argumentos:

x, y -> f(y, x)
f(_2, _1)  # can't do with other suggested _ syntax

Creo que estaría bien que la sintaxis permitiera la numeración explícita de argumentos anónimos ( _1 , _2 , _3 ... etc.), Pero el problema principal sigue en pie : ¿cuándo exactamente promueves una función parcialmente aplicada a una lambda concisa? ¿Y qué es exactamente el cuerpo lambda? Probablemente me equivocaría por el lado de ser explícito (con un delimitador) en lugar de usar implícitamente algún tipo de reglas de promoción complejas. Que debería

foo(_1, _1 + _2  + f(_1, v1) + g(_2, v3), _3 * _2, v2) + g(_4, v4) +
 f(_2, v2) + g(_3, v5) + bar(_1, v6)

significa exactamente? Usando un delimitador (usaré λ ) las cosas son algo más claras:

λ(foo(_1, λ(_1 + _2)  + λ(f(_1, v1) + g(_2, v3)), _3 * _2, v2) + g(_4, v4)) + 
λ(f(_2, v2) + g(_3, v5) + bar(_1, v6))

Obviamente, esto es un MethodError: + has no method matching +(::Function, ::Function) , pero al menos puedo decirlo por la forma en que está escrito.

Creo que @StefanKarpinski podría estar en algo cuando dijo que hay algunas posiciones sintácticas aparentemente obvias que las expresiones pueden tomar y que implican fuertemente que son cuerpos funcionales. Arrow precedence se ocupa de varios de estos, pero todavía hay algunos casos confusos. Prometedor, pero definitivamente requiere un pensamiento cuidadoso.

Definitivamente se trata de una compensación entre la concisión frente a la generalidad y la legibilidad. Por supuesto, no tiene sentido introducir algo que sea menos conciso que la notación -> . Pero también creo que un delimitador parece útil.

Tal vez no sea lo suficientemente conciso, pero ¿qué tal una versión de prefijo de -> que capture _ argumentos? P.ej

(-> 2f(_) + 1)

Supongo que la forma del prefijo debería tener una precedencia bastante baja. En realidad, esto podría permitir omitir los paréntesis en algunos casos, por ejemplo

map(->_ + 1, x)

En este momento estoy jugando con la implementación de https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665

Como macro, eso transforma todas esas ocurrencias en la línea.
La parte complicada es implementar la precedencia.

Probablemente no lo terminaré en las próximas 12 horas porque es hora de casa aquí
(tal vez en los próximos 24, pero podría tener que irme)
De todos modos, una vez hecho esto, podemos jugar con él.

Una extraña que sale de https://github.com/JuliaLang/julia/issues/5571#issuecomment -157424665

  • f(_,_)x,y -> f(x,y) (esto es razonable)
  • f(_,2_) → ??

    • f(_,2_)x,y -> f(x,2y) (razonable)

    • f(_,2_)x-> f(x,y->2y) (lo que creo que sugiere la regla y lo que produce mi prototipo)

Pero no estoy seguro de haberlo hecho bien.

Así que aquí está mi prototipo.
http://nbviewer.ipython.org/gist/oxinabox/50a1e17cfb232a7d1908

De hecho, definitivamente falla algunas de las pruebas.

No es posible considerar el horquillado en la capa AST actual; a menudo (¿siempre?) Ya están resueltos.

Todavía es suficiente para jugar, creo

Algunas reglas de magrittr en R que pueden ser útiles:

Si una cadena comienza con. , es una función anónima:

. %>% `+`(1)

es lo mismo que la función (x) x + 1

Hay dos modos de encadenamiento:
1) Encadenamiento insertando como primer argumento, así como a los puntos que aparezcan.
2) Encadenando solo a los puntos que aparecen.

El modo predeterminado es el modo 1. Sin embargo, si el punto aparece por sí mismo como un argumento de la función que se está encadenando, entonces magrittr cambia al modo 2 para ese paso en la cadena.

Entonces

2 %>% `-`(1) 

es 2 - 1,

y

1 %>% `-`(2, . )

también es 2 - 1

El modo 2 también se puede especificar entre corchetes:

2 %>% { `-`(2, . - 1) }

sería lo mismo que 2 - (2 - 1).

También una nota de que poder cambiar inteligentemente entre el modo 1 y el modo 2 resuelve casi por completo el problema de que Julia no es muy consistente acerca de tener el argumento al que probablemente se encadenaría en la primera posición. También me olvidé de señalar que los corchetes pueden permitir evaluar un fragmento de código. Aquí hay un ejemplo del manual magrittr:

iris%>%
{
n <- muestra (1:10, tamaño = 1)
H <- cabeza (., N)
T <- cola (., N)
rbind (H, T)
}%>%
resumen

Esta es solo una idea a medio formar en este momento, pero me pregunto si hay alguna manera de resolver los problemas de "lambda conciso" y "variable ficticia" al mismo tiempo modificando el constructor de Tuple de modo que un valor faltante devuelva un lambda que devuelve una tupla en lugar de una tupla? Entonces, (_, 'b', _, 4) devolvería (x, y) -> (x, 'b', y, 4) .

Entonces, si cambiamos sutilmente la semántica de la llamada a la función de modo que foo(a, b) significa "aplicar foo a la Tupla (a, b) o si el argumento es una función, entonces aplica foo a la tupla devuelta por la función ". Esto haría que foo(_, b, c)(1) equivalente a apply(foo, ((x) -> (x, b, c))(1)) .

Creo que esto todavía no resuelve el problema de la notación infija, pero personalmente estaría contento con lambdas concisos que solo funcionan con llamadas a funciones entre paréntesis. Después de todo, 1 + _ siempre se puede reescribir +(1, _) si es absolutamente necesario.

@jballanc Sin embargo, la construcción de tuplas y la aplicación de funciones son dos conceptos bastante distintos. Al menos, a menos que mi comprensión de la semántica de julia sea seriamente defectuosa.

@samuela Lo que quise decir con eso es que foo(a, b) es equivalente a foo((a, b)...) . Es decir, los argumentos de una función pueden considerarse conceptualmente como una tupla, incluso si la tupla nunca se construye en la práctica.

@He intentado leer esta discusión, pero es demasiado larga para hacer un seguimiento de todo lo que se ha dicho, lo siento si repito más de lo necesario aquí.

Solo me gustaría votar para hacer de |> un complemento de la "magia" do . Por lo que puedo ver, la forma más fácil de hacerlo sería dejar que signifique que

3 |> foo == foo(3) # or foo() instead of just foo, but it would be nice if the parentheses were optional
3 |> foo(1) == foo(1, 3)
3 |> foo(1,2) == foo(1,2,3)

En otras palabras, a |> f(x) hace con el _último_ argumento lo que f(x) do; a; end hace con el _primer_. Esto inmediatamente lo haría compatible con map , filter , all , any et. al., sin agregar la complejidad de establecer el alcance de los parámetros _ , y dada la sintaxis do ya existente, no creo que suponga una carga conceptual irrazonable para los lectores del código.

Mi principal motivación para usar un operador de tubería como este son las tuberías de colección (ver # 15612), que creo que es una construcción increíblemente poderosa y que está ganando terreno en muchos idiomas (lo que indica que es una característica que la gente quiere y una ellos entenderán).

Esa es la macro @>> de https://github.com/MikeInnes/Lazy.jl.

@malmaud ¡Agradable! Me gusta que esto ya sea posible: D

Sin embargo, la diferencia de legibilidad entre estas dos variantes es realmente grande:

# from Lazy.jl
@> x g f(y, z)

# if this became a first-class feature of |>
x |> g |> f(y, z)

Creo que el principal problema de legibilidad es que no hay pistas visuales que indiquen dónde están los límites entre las expresiones: los espacios en x g y g f(x, afectarán significativamente el comportamiento del código, pero el espacio en f(x, y) no lo hará.

Dado que @>> ya existe, ¿qué tan factible es agregar este comportamiento a |> en 0.5?

(No quiero gastar demasiada energía en la notación del operador, así que no descarte esta propuesta únicamente en el tema de la notación. Sin embargo, podemos notar que "piping" parece una noción natural para esto (cf el término "canalización de colección"), y que, por ejemplo, F # ya usa |> para esto, aunque, por supuesto, tiene una semántica ligeramente diferente ya que las funciones de F # son diferentes a las funciones de Julia).

Sí, seguro que estoy de acuerdo en el frente de la legibilidad. No sería técnicamente un desafío hacer que |> comporte como lo describe en 0.5, es solo una pregunta de diseño.

De manera similar, sería posible hacer que las funciones de análisis macro @>> Lazy.jl encadenaran por |> .

Hm. Empezaré a trabajar en un PR para Lazy.jl entonces, pero eso no significa que me gustaría que esto no estuviera en 0.5 :) No creo que sepa lo suficiente sobre el analizador de Julia y Sin embargo, cómo cambiar el comportamiento de |> para ayudar con eso, a menos que obtenga una tutoría bastante extensa.

No creo que lo mencioné en este hilo, pero tengo otro paquete de encadenamiento, ChainMap.jl. Siempre se sustituye por _ y se inserta condicionalmente en el primer argumento. También intenta integrar la cartografía. Ver https://github.com/bramtayl/ChainMap.jl

Entonces, nuestra lista actual de varios esfuerzos, etc.
Creo que vale la pena que la gente los revise (idealmente antes de opinar, pero w / e)
todos son ligeramente diferentes.
(Estoy intentando ordenar cronológicamente).

Paquetes

Prototipos sin paquete

Relacionado:


Quizás esto debería editarse en una de las publicaciones principales.

actualizado: 2020-04-20

Esto es más un experimento de sistema de tipos que un intento real de implementar una aplicación parcial, pero aquí hay uno extraño: https://gist.github.com/fcard/b48513108a32c13a49a387a3c530f7de

uso:

include("partial_underscore_generated.jl")
using GeneratedPartial

const sub = partialize(-)
sub(_,2)(1) == 1-2
sub(_,_)(1,2) == 1-2
sub(_,__)(1)(2) == 1-2
sub(__,_)(2)(1) == 1-2 #hehehe

# or
<strong i="8">@partialize</strong> 2 Base.:+ # evily inserts methods in + and allows partializations for 2 arguments
(_+2)(1) == 1+2

# fun:
sub(1+_,_)(2,3) == sub(1+2,3)
sub(1+_,__)(2)(3) == sub(1+2,3)
(_(1)+_)(-,1) == -1+1

# lotsafun:
appf(x::Int,y::Int) = x*y
appf(f,x) = f(x)
<strong i="9">@partialize</strong> 2 appf

appf(1+_,3)(2) == appf(1+2,3)
appf(?(1+_),3) == appf(x->(1+x), 3)
appf(?sub(_,2),3) == appf(x->x-2,3) # I made a method *(::typeof(?),::PartialCall), what of it!!?

# wooooooooooooooooooooooooooooooooo
const f = sub
f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,f(_,_)))))))))(1,2,3,4,5,6,7,8,9,10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))
f(_,f(__,f(___,f(____,f(_____,f(______,f(_______,f(________,f(_________,__________)))))))))(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) == f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,10)))))))))

# this answers Stefan's concern (which inspired me to make this hack in the first place)
#
#    const pmap = partialize(map)
#    map(f(_,a),   v) == map(x->f(x,a), v)
#    pmap(?f(_,a), v) == map(x->f(x,a), v)
#    pmap(f(_,a),  v) == x->map(f(x,a), v)
#
# it adds a few other issues, of course...


Ciertamente no es una propuesta de implementación, pero me parece divertido jugar con él, y tal vez alguien pueda sacar la mitad de una buena idea.

PD: Olvidé mencionar que @partialize también funciona con rangos y literales de matriz de enteros:

<strong i="15">@partialize</strong> 2:3 Base.:- # partialized for 2 and 3 arguments!

(_-_-_)(1,2,3) == -4
(_-_+_)(1,2,3) == +2

De acuerdo, he estado pensando en la composición de funciones, y aunque en mi opinión está claro en el caso de SISO, creo que estaba pensando en cómo uso el MISO de Julia (¿cuasi-MIMO?) En pequeños bloques de código encadenados.

Me gusta el REPL. Mucho. Es lindo, es genial, te permite experimentar como MATLAB o Python o el shell. Me gusta tomar código de un paquete o archivo y copiarlo y pegarlo en el REPL, incluso en varias líneas de código. El REPL evalúa eso en cada línea y me muestra lo que está pasando.

También devuelve / define ans después de cada expresión. Todos los usuarios de MATLAB lo saben (aunque en esta etapa, ¡este es un mal argumento!). Probablemente la mayoría de los usuarios de Julia lo hayan visto / usado antes. Uso ans en las situaciones extrañas en las que estoy jugando con algo por partes, y me doy cuenta de que quiero agregar otro paso a lo que escribí anteriormente. No me gusta que su uso sea algo destructivo, por lo que tiendo a evitarlo cuando es posible, pero _todas_ las propuestas aquí se refieren a tiempos de retorno de solo un paso de composición.

Para mí, _ ser mágico es simplemente _odd_, pero entiendo que muchas personas pueden no estar de acuerdo. Entonces, si _quiero_ copiar y pegar el código de los paquetes en el REPL y ver cómo se ejecuta, y si quiero una sintaxis que no parece mágica, entonces podría proponer:

<strong i="12">@repl_compose</strong> begin
   sin(x)
   ans + 1
   sqrt(ans)
end

Si la función devuelve múltiples salidas, puedo insertar ans[1] , ans[2] , etc. en la siguiente línea. Se ajusta perfectamente al modelo de composición de un solo nivel, el modelo MISO de Julia, y ya es una sintaxis de Julia muy estándar, pero no en archivos.

La macro es fácil de implementar: simplemente convierta Expr(:block, exprs...) en Expr(:block, map(expr -> :(ans = $expr), exprs) (también un let ans al principio, y tal vez podría haber una versión que haga una función anónima que tome una entrada o algo?). No tendría que vivir en la base (sin embargo, el REPL está integrado en Julia, y va con eso).

De todos modos, ¡solo mi perspectiva! ¡Este fue un hilo largo, que no he visto en mucho tiempo!

También devuelve / define ans después de cada expresión. Todos los usuarios de MATLAB lo saben (aunque en esta etapa, ¡este es un mal argumento!).

En realidad, el otro argumento es que si se usa _ en el encadenamiento de funciones, entonces el REPL también debería devolver _ lugar de ans (para mí, esto sería suficiente para eliminar la magia").

Existe una gran cantidad de precedentes para usar _ como el "valor de ti" en los idiomas. Por supuesto, eso entra en conflicto con la idea propuesta de usar _ como un nombre que descarta asignaciones y para lambdas más tersas.

Estoy bastante seguro de que esto existe en algún lugar de Lazy.jl como <strong i="5">@as</strong> and begin ...

La idea de usar . para encadenar se descartó al principio de la conversación, pero tiene una larga historia de utilidad y minimizaría la curva de aprendizaje para los usuarios de otros idiomas. La razón por la que es importante para mí es porque

type Track
  hit::Array{Hit}
end
type Event
  track::Array{Track}
end

event.track[12].hit[43]

me da el 43. ° hit de la duodécima pista de un evento cuando track y hit son matrices simples, entonces

event.getTrack(12).getHit(43)

Debería darme lo mismo si tienen que ser servidos de forma dinámica. No quiero tener que decir

getHit(getTrack(event, 12), 43)

Se pone peor cuanto más profundo vas. Dado que estas son funciones simples, hace que el argumento sea más amplio que el del encadenamiento de funciones (a la Spark).

Estoy escribiendo esto ahora porque acabo de enterarme de los rasgos de Rust , que podrían ser una buena solución en Julia por las mismas razones. Al igual que Julia, Rust solo tiene datos structs (Julia type ), pero también tiene impl para funciones de enlace al nombre de un struct . Por lo que puedo decir, es puro azúcar sintáctico, pero permite la notación de puntos que describí anteriormente:

impl Event {
  fn getTrack(&self, num: i32) -> Track {
    self.track[num]
  }
}

impl Track {
  fn getHit(&self, num: i32) -> Track {
    self.track[num]
  }
}

que en Julia podría ser

impl Event
  function getTrack(self::Event, num::Int)
    self.track[num]
  end
end

impl Track
  function getHit(self::Track, num::Int)
    self.hit[num]
  end
end

La sintaxis propuesta anteriormente no hace ninguna interpretación de self : es solo un argumento de función, por lo que no debería haber conflictos con el envío múltiple. Si desea hacer una interpretación mínima de self , puede hacer que el tipo del primer argumento esté implícito, de modo que el usuario no tenga que escribir ::Event y ::Track en cada función, pero lo bueno de no hacer ninguna interpretación es que los "métodos estáticos" son solo funciones en el impl que no tienen self . (Rust los usa para fábricas de new ).

A diferencia de Rust, Julia tiene una jerarquía en types . También podría tener una jerarquía similar en impls para evitar la duplicación de código. Una POO estándar podría construirse haciendo que las jerarquías de datos type y método impl exactamente iguales, pero esta duplicación estricta no es necesaria y en algunos casos no es deseable.

Hay un punto difícil con esto: supongamos que nombré mis funciones track y hit en impl , en lugar de getTrack y getHit , por lo que entraron en conflicto con las matrices track y hit en el type . Entonces, ¿ event.track devolvería la matriz o la función? Si lo usa inmediatamente como una función, eso podría ayudar a eliminar la ambigüedad, pero types puede contener Function objetos. Tal vez solo aplique una regla general: después del punto, primero marque el impl , luego marque el type ?

Pensándolo bien, para evitar tener dos "paquetes" para lo que es conceptualmente el mismo objeto ( type y impl ), ¿qué tal esto?

function Event.getTrack(self, num::Int)
  self.track[num]
end

para vincular la función getTrack a instancias de tipo Event tal que

myEvent.getTrack(12)

produce el mismo código de bytes que la función aplicada a (myEvent, 12) ?

Lo nuevo es la sintaxis typename-dot-functionname después de la palabra clave function y cómo se interpreta. Esto aún permitiría el envío múltiple, un self similar a Python si el primer argumento es el mismo que el tipo al que está vinculado (o se deja implícito, como se indicó anteriormente), y permite un "método estático" si el primer argumento no está presente o se escribe de manera diferente al tipo al que está vinculado.

@jpivarski ¿Hay alguna razón por la que piensas que la sintaxis de puntos (que, al leer este hilo, tiene muchas desventajas) es mejor que alguna otra construcción que permite el encadenamiento? Sigo pensando que crear algo como do pero para el último argumento , respaldado por alguna forma de sintaxis de canalización (por ejemplo, |> ) sería la mejor manera de avanzar:

event |> getTrack(12) |> getHit(43)

La razón principal por la que puedo ver que algo como el enfoque de Rust podría ser mejor es que usa efectivamente el lado izquierdo como un espacio de nombres para funciones, por lo que es posible que pueda hacer cosas como parser.parse sin entrar en conflicto con el Función Julia Base.parse . Yo estaría a favor de proporcionar tanto la propuesta de Rust como los ribetes estilo Hack.

@tlycken Sin
Recordar la frecuencia de |> vs llamada puede resultar confuso, ya que realmente no da ninguna pista.
(Tampoco se sugieren varias de las otras opciones).

Considerar

foo(a,b) = a+b
foo(a) = b -> a-b

2 |> foo(10) == 12   #Pipe Precedence > Call Precedence 
2 |> foo(10) == 8     #Pipe Precedence < Call Precedence   

@oxinabox En realidad, no estoy sugiriendo que sea "solo" un operador normal, sino más bien un elemento de sintaxis del lenguaje; 2 |> foo(10) desazúcares a foo(10, 2) casi de la misma manera foo(10) do x; bar(x); end desazúcares a foo(x -> bar(x), 10) . Eso implica la precedencia de la tubería sobre la precedencia de la llamada (que, creo, es lo que tiene más sentido de todos modos).

Solo en el tema de la sintaxis, . es menos molesto visualmente y ciertamente más estándar que |> . Puedo escribir una cadena fluida de funciones separadas por . sin espacios (un carácter cada una) y cualquiera podría leerlo; con |> , tendría que agregar espacios (cuatro caracteres cada uno) y sería un salto rápido visual para la mayoría de los programadores. La analogía con el | secuencias de comandos de shell es agradable, pero no se reconoce de inmediato debido al > .

¿Estoy leyendo este hilo correctamente que el argumento en contra del punto es que es ambiguo si debería obtener un dato de miembro de type o una función de miembro de impl (mi primera sugerencia) o la función espacio de nombres (mi segunda sugerencia)? En la segunda sugerencia, las funciones definidas en el espacio de nombres de funciones creado por type deben definirse _después_ de que se define type , por lo que puede negarse a eclipsar un dato de miembro allí mismo.

Agregar ambos puntos de espacio de nombres (mi segunda sugerencia) y |> estaría bien para mí; son bastante diferentes en propósito y efecto, a pesar de que ambos pueden usarse para encadenamiento fluido. Sin embargo, |> como se describe anteriormente no es completamente simétrico con do , ya que do requiere que el argumento que llena sea una función. Si está diciendo event |> getTrack(12) |> getHit(43) , entonces |> aplica a las no funciones ( Events y Tracks ).

Si está diciendo event |> getTrack(12) |> getHit(43) , entonces |> aplica a las no funciones ( Events y Tracks ).

En realidad, no, se aplica a los encantamientos de la función _a la derecha_ insertando su operando izquierdo como último argumento de la llamada a la función. event |> getTrack(12) es getTrack(12, event) por lo que estaba a la derecha, no por lo que estaba a la izquierda.

Esto tendría que significar a) precedencia sobre las llamadas a funciones (ya que es una reescritura de la llamada) yb) orden de aplicación de izquierda a derecha (para hacerlo getHit(43, getTrack(12, event)) lugar de getHit(43, getTrack(12), event) ) .

Pero la firma getTrack's es

function getTrack(num::Int, event::Event)

así que si event |> getTrack(12) inserta event en getTrack's último argumento, está poniendo Event en el segundo argumento, no Function . Acabo de probar el equivalente con do y el primer argumento, y Julia 0.4 se quejó de que el argumento debe ser una función. (Posiblemente porque do event end se interpreta como una función que devuelve event , en lugar del event sí).

El encadenamiento de funciones me parece un tema independiente de gran parte de lo que se discute en torno a la sintaxis del punto ( . ). Por ejemplo, @jpivarski , ya puede lograr gran parte de lo que menciona de Rust en Julia sin ninguna característica nueva:

type TownCrier
  name::AbstractString
  shout::Function

  function TownCrier(name::AbstractString)
    self = new(name)
    self.shout = () -> "HELLO, $(self.name)!"
    self
  end
end

tc = TownCrier("Josh")
tc.shout()                                #=> "HELLO, Josh!"
tc.name = "Bob"
tc.shout()                                #=> "HELLO, Bob!"

Sin tratar de descarrilar demasiado la conversación, sugeriría que lo que realmente necesitamos resolver es cómo hacer una función eficiente en Julia. Las preguntas sobre cómo especificar posiciones para argumentos en una cadena de funciones desaparecerían si tuviéramos una buena forma de curry funciones. Además, las construcciones como las anteriores serían más limpias si el cuerpo de la función pudiera especificarse y simplemente cursar con "self" en la construcción.

@andyferris He estado usando Python y realmente me gusta _ refiriéndome al resultado de la expresión anterior. Sin embargo, no funciona dentro de las funciones. Sería genial si pudiéramos hacer que funcione en cualquier lugar: dentro de los bloques de inicio, funciones, etc.

Creo que esto podría reemplazar totalmente el encadenamiento. No deja ninguna ambigüedad sobre el orden de los argumentos. Por ejemplo,

begin
    1
    vcat(_, 2)
    vcat(3, _)
end

# [3, 1, 2]

Como mencionó @MikeInnes , esto ya está disponible en @_ en Lazy.jl (y aunque no funcionó de esa manera originalmente, ChainMap.jl también usa este tipo de encadenamiento ahora).

Con suerte, esto podría funcionar junto con la fusión de puntos, al menos dentro de los bloques

begin
    [1, 2, 3]
    .+(_, 2)
    .*(_, 2)
    .-(10, _)
end

o, usando la sintaxis @chain_map ,

begin
    ~[1, 2, 3]
    +(_, 2)
    *(_, 2)
    -(10, _)
end

Actualmente hay una forma de encadenar funciones con objetos si la función está definida dentro del constructor. Por ejemplo, la función Obj.times:

type Obj
    x
    times::Function
    function Obj(x)
       this = new(x)
       this.times =  (n) -> (this.x *= n; this)
       this
    end
end

>>>Obj(2).times(3)
Obj(6,#3)

¿Qué pasa con la implementación de funciones miembro (funciones especiales) definidas fuera de la definición de tipo? Por ejemplo, la función Obj.times se escribiría como:

member function times(this::Obj, n)
     this.x *= n
     return this
end

>>>Obj(2).times(3)
Obj(6,#3)

donde member es una palabra clave especial para funciones miembro.
Las funciones miembro tienen acceso a los datos del objeto. Más tarde, se llamarán usando un punto después de la variable de objeto.
La idea es reproducir el comportamiento de las funciones definidas dentro de los constructores utilizando funciones "miembro" definidas fuera de la definición de tipo.

Algo como esto se hace en Rust con la sintaxis del método . Es conceptualmente diferente del encadenamiento de funciones, aunque podría usarse para hacer que el encadenamiento se vea como lo hace en algunos lenguajes OO. Probablemente sea mejor abordar los problemas por separado.

He leído esto y algunos temas relacionados, aquí está mi propuesta:

Encadenamiento básico :
in1 |> function1
Igual que: in1 |> function1(|>)

in2 |> function2(10)
Igual que: in2 |> function2(|>,10)

Aún más encadenamiento:
in1 |> function1 |> function2(10,|>)

Ramificación y fusión de cadenas:
Rama dos veces con sucursales out1 , out2 :
function1(a) |out1>
function2(a,b) |out2>

Utilice la sucursal out1 y out2 :
function3(|out1>,|out2>)

¿Qué pasa con la pereza?
¿Necesitamos algo como la convención function!(mutating_var) ?
Para las funciones perezosas podríamos usar function?() ...

Y al usar la sangría correctamente, es fácil rastrear visualmente las dependencias de datos en el gráfico de llamadas asociado.

Acabo de jugar con un patrón para el encadenamiento de funciones con el operador |> . Por ejemplo, estas definiciones:
julia

Filtrar

MiFiltro inmutable {F}
flt :: F
fin

función (mf :: MyFilter) (fuente)
filtro (mf.flt, fuente)
fin

función Base.filter (flt)
MyFilter (flt)
fin

Tomar

MyTake inmutable
n :: Int64
fin

function (mt :: MyTake) (fuente)
take (fuente, mt.n)
fin

función Base.take (n)
MyTake (n)
fin

Mapa

MyMap inmutable {F}
f :: F
fin

función (mm :: MyMap) (fuente)
mapa (mm.f, fuente)
fin

función Base.map (f)
Mi mapa (f)
fin
enable this to work: julia
1:10 |> filtro (i-> i% 2 == 0) |> tomar (2) |> mapa (i-> i ^ 2) |> recolectar
`` Essentially the idea is that functions like filter return a functor if they are called without a source argument, and then these functors all take one argument, namely whatever is "coming" from the left side of the |> . The |> `` luego simplemente encadena todos estos functores juntos.

filter etc.también podría devolver una función anónima que toma un argumento, no estoy seguro de cuál de estas opciones sería más eficaz.

En mi ejemplo, estoy sobrescribiendo map(f::Any) en Base, realmente no entiendo qué hace la definición existente de map ...

Se me ocurrió este patrón, y mi mirada algo superficial a mi alrededor no mostró ninguna discusión sobre algo así. ¿Qué piensa la gente? ¿Podría ser útil esto? ¿Puede la gente pensar en los inconvenientes de esto? Si esto funciona, tal vez el diseño existente sea lo suficientemente flexible como para permitir una historia de encadenamiento bastante completa.

Esto no parece viable para funciones arbitrarias, ¿solo aquellas para las que se ha definido MyF?

Sí, esto solo funciona para las funciones que se habilitan. Claramente no es una solución muy general, y en cierto sentido es la misma historia que con la vectorización, pero aún así, dado que no todo lo que esperaríamos llegará a 1.0, este patrón podría permitir una gran cantidad de escenarios a los que la gente tuvo que recurrir. macros ahora mismo.

Básicamente, la idea es que funciones como filtro devuelven un functor si se llaman sin un argumento fuente, y luego todos estos functores toman un argumento, es decir, lo que "viene" del lado izquierdo de |>.

Esta es, casi exactamente, la esencia de los transductores de Clojure. La diferencia notable es que Clojure construyó transductores sobre el concepto de reductores. En resumen, cada función que opera en una colección se puede descomponer en una función de "mapeo" y una función de "reducción" (incluso si la función de "reducción" es simplemente concat ). La ventaja de representar las funciones de colección de esta manera es que puede reorganizar la ejecución para que todo el "mapeo" se pueda canalizar (especialmente bueno para colecciones grandes). Los transductores, entonces, son solo una extracción de estos "mapeos" devueltos cuando se los llama sin una colección para operar.

No es necesario que esto sea tan complicado. Las funciones pueden optar por curry con cierres:

Base.map(f)    = (xs...) -> map(f, xs...)
Base.filter(f) = x -> filter(f, x)
Base.take(n)   = x -> take(x, n)

Por supuesto, esto no es algo que deba hacer un paquete, ya que cambia el significado de estos métodos para todos los paquetes. Y hacerlo poco a poco de esta manera no es terriblemente intuitivo: ¿qué argumentos deberían tener prioridad?

Preferiría una solución sintáctica de sitio de llamadas como la que se discutió anteriormente, reduciendo f(a, _, b) a x -> f(a, x, b) . Sin embargo, es complicado, como se señaló en la larga discusión anterior.

No es necesario que esto sea tan complicado. Las funciones pueden optar por curry con cierres

Sí, ya sugerí que anteriormente, simplemente no estaba seguro de si hay una diferencia de rendimiento entre estos dos.

¿Qué argumentos deberían tener prioridad?

Sí, y luego tenemos cosas como filter y take , donde en un caso tenemos la colección como el primero y en el otro como el último argumento ... siento que al menos para operaciones de tipo iterador, normalmente puede haber una respuesta obvia a esa pregunta.

Una vez que _ sea ​​un símbolo especial disponible

Sí, estoy totalmente de acuerdo en que existe una solución más general, y la de @malmaud podría ser.

No hay diferencia de rendimiento ya que los cierres esencialmente solo generan el código que escribió a mano de todos modos. Pero como solo está currando, puede escribir una función para que lo haga por usted ( curry(f, as...) = (bs...) -> f(as..., bs...) ). Eso se encarga del mapa y el filtro; También ha habido propuestas en el pasado para implementar un curry que implemente un valor centinela como curry(take, _, 2) .

Vine aquí, porque actualmente estoy aprendiendo tres idiomas: D, Elixir y ahora Julia. En D existe la sintaxis uniforme de llamada a función , como en Rust, en Elixir tienes el operador de tubería . Básicamente, ambos implementan el tipo de encadenamiento de funciones sugerido aquí y realmente me gustó esta característica en ambos lenguajes, ya que es fácil de entender, parece fácil de implementar y puede hacer que el código usando flujos y otros tipos de canalizaciones de datos sea mucho más legible.

Solo he visto un breve tutorial sobre la sintaxis de Julia hasta ahora, pero inmediatamente busqué en Google esta función, porque esperaba que Julia también tuviera algo como esto. Así que supongo que este es un +1 para esta solicitud de función de mi parte.

Hola amigos,

Permítame hacer +1 en esta solicitud de función. Esto es muy necesario. Considere la siguiente sintaxis de Scala.

Array(1,2,3,4,5)
  .map(x => x+1)
  .filter(x => x > 5)
  .reduce(_ + _)

Las personas que han utilizado Spark u otras herramientas de big data basadas en MapReduce estarán muy familiarizadas con esta sintaxis y habrán escrito trabajos grandes y complicados de esta manera. Incluso R, comparativamente antiguo, permite lo siguiente.

c(1,2,3,4,5) %>%
  {. + 1} %>%
  {.[which(. > 5)]} %>%
  sum

Tenga en cuenta el uso inteligente de bloques de código como sustituto de la programación funcional adecuada, no el más bonito, pero poderoso. En Julia, puedo hacer lo siguiente.

[1,2,3,4,5] |> 
  _ -> map(__ -> __ + 1, _) |>
  _ -> filter(__ -> __ < 5, _) |>
  _ -> reduce(+, _)

Pero esto es horrible y feo. Si no podemos tener código orientado a objetos a la Scala, los operadores de tubería se vuelven increíblemente importantes. La solución más simple es que la tubería ingrese el _primer argumento_, a menos que se use explícitamente un comodín como _, pero esto solo tendría sentido si se cambiara map para tomar la estructura de datos como primer argumento y función como segundo.

También debería haber algún equivalente de Array(1,2,3).map(_ + 1) de Scala para evitar un exceso de _ -> _ + 1 y una sintaxis similar. Me gusta la idea anterior donde [1,2,3] |> map(~ + 1, _) se traduce a map(~ -> ~ + 1, [1,2,3]) . Gracias por mirar.

Para este último, tenemos la transmisión con sintaxis compacta [1, 2, 3] .+ 1 Es bastante adictivo. Algo así para la reducción (y tal vez el filtrado) sería increíblemente genial, pero parece una gran pregunta.

Es un punto razonable notar que tanto piping como do luchan por el primer argumento de la función.

Recordaré que viene algo nuevo al hilo, que tenemos,
no uno, ni dos, sino CINCO paquetes que proporcionan extensiones a la funcionalidad de canalización SISO base de julia, hacia las sintaxis sugeridas.
ver lista en: https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539

Es un punto razonable señalar que tanto la canalización como la lucha por el primer argumento de la función.

Si tuviéramos una funcionalidad de tubería adicional en la base, esa posición no estaría marcada con _ etc.
Entonces creo que agregaría argumentos a la posición final, no a la primera.
eso lo haría más como "pretender curry / aplicación parcial"

Mi publicación anterior está destinada a ser un ejemplo simple diseñado para ilustrar los problemas de sintaxis en cuestión.

En realidad, a menudo se utilizan cientos de operaciones en una cadena, muchas de ellas no estándar. Imagine trabajar con big data en lenguaje natural. Escribe una secuencia de transformaciones que toman una cadena de caracteres, filtra los caracteres chinos, los divide por espacios en blanco, filtra palabras como "el", transforma cada palabra en una forma "estandarizada" a través de la herramienta de software de caja negra utilizada por su web servidor, agregue información sobre qué tan común es cada palabra a través de otra herramienta de caja negra, sume ponderaciones sobre cada palabra única, otras 100 operaciones, etc.

Estas son situaciones que estoy considerando. Hacer tales operaciones sin usar llamadas a métodos o tuberías no es un comienzo debido a su tamaño.

No sé cuál es el mejor diseño, simplemente animo a todos a considerar los casos de uso y una sintaxis más elaborada que la que existe actualmente.

Esto debería funcionar en Juno y Julia 0.6

`` `{julia}
usando LazyCall
@lazy_call_module_methods Generador base
@lazy_call_module_methods Filtro de iterador

usando ChainRecursive
start_chaining ()


```{julia}
[1, 2, 3, 4, 5]
<strong i="14">@unweave</strong> ~it + 1
Base.Generator(it)
<strong i="15">@unweave</strong> ~it < 5
Iterators.filter(it)
reduce(+, it)

Tengo una pregunta sobre la sintaxis que he visto en los comentarios sobre este tema:
https://stackoverflow.com/questions/44520097/method-chaining-in-julia

@somedadaism , los problemas son por problemas y no para "publicitar" preguntas de desbordamiento de pila. Además, la gente de Julia es muy activa en SO y (aún más) en https://discourse.julialang.org/. Me sorprendería mucho que no obtuviera una respuesta a la mayoría de las preguntas allí muy rápidamente. ¡Y bienvenido a Julia!

Dios, increíble lo complicado que puede ser esto oO. +1 para una sintaxis decente. Para mí, el uso principal de las tuberías también es trabajar con datos (marcos). Piense en dplyr. Personalmente, no me importa pasar primero / último como predeterminado, pero supongo que la mayoría de los desarrolladores de paquetes harán que sus funciones acepten datos como primer argumento, ¿y qué pasa con los argumentos opcionales? +1 por algo como

1 |> sin |> sum(2, _)

Como se mencionó anteriormente, la legibilidad y la simplicidad son muy importantes. No me gustaría perderme todo el estilo dplyr / tidyverse de hacer cosas para el análisis de datos ...

Me gustaría agregar que también encuentro muy útil la sintaxis multilínea de Elixir para el operador de tubería.

1
|> sin
|> sum(2)
|> println

Es el equivalente a println(sum(sin(1),2))

Solo para señalar una propuesta en el mundo javascript . Usan el operador ? lugar de _ o ~ que ya tenemos el significado de ( _ para ignorar algo y ~ como bit a bit no o formulario). Dado que actualmente usamos ? igual que javascript, también podríamos usarlo para el marcador de posición de currying.

así es como se ve su propuesta (está en javascript, pero también es válida en julia :)

const addOne = add(1, ?); // apply from the left
addOne(2); // 3

const addTen = add(?, 10); // apply from the right
addTen(2); // 12

// with pipeline
let newScore = player.score
  |> add(7, ?)
  |> clamp(0, 100, ?); // shallow stack, the pipe to `clamp` is the same frame as the pipe to `add`.

const maxGreaterThanZero = Math.max(0, ...);
maxGreaterThanZero(1, 2); // 2
maxGreaterThanZero(-1, -2); // 0

Un resumen porque empecé a escribir uno por otros motivos.
Consulte también mi lista anterior paquetes relacionados .

Cualquier juego con _ no se rompe y se puede hacer en 1.x, porque https://github.com/JuliaLang/julia/pull/20328

Todo esto se reduce a dos opciones principales (además del status quo).
Ambos pueden (para todos los efectos) implementarse con una macro para reescribir la sintaxis.

Jugando con _ para hacer funciones anónimas

Las Lambdas concisas de @StefanKarpinski , o una sintaxis similar donde la presencia de un _ (en una expresión RHS), indica que toda la expresión es una función anónima.

  • esto casi puede

    • Lo único que no se puede hacer es que (_) no sea lo mismo que _ . Que es solo la función de identidad, así que realmente no importa

    • Esto se aplicaría en todas partes, por lo que no solo sería útil con |> , sino también, por ejemplo, al escribir cosas de forma compacta como map(log(7,_), xs) o log(7, _).(xs) para tomar el registro con base 7 de cada elemento de xs .

    • Personalmente estoy a favor de esto, si es que estamos haciendo algo.

Jugando con |> para que realice sustituciones

Las opciones incluyen:

  • Haz que su RHS actúe como si estuvieran curry

    • en realidad, creo que esto se está rompiendo (aunque tal vez haya una versión que no se rompa que verifique la tabla de métodos. Creo que, en cambio, es confuso)

  • Haga que _ act sea especial (consulte las opciones anteriores y / o varias formas de falsificarlo mediante reescritura)

    • una forma de hacer esto sería permitir la creación de macros infijas, luego se podría escribir @|>@ y definirlo como desee en los paquetes (esto ya se ha cerrado una vez https://github.com/JuliaLang/julia/ números / 11608)

    • o darle esas propiedades especiales intrínsecamente

  • Tenemos toneladas de implementaciones de macros para hacer esto, como dije, vea mi lista de paquetes relacionados
  • Algunas personas también proponen cambiarlo para que (a diferencia de todos los demás operadores) pueda hacer que una expresión en la línea antes de que ocurra no termine. Para que puedas escribir
a
|> f
|>g

En lugar de la corriente:

a |>
f |>
g

(No es posible implementar esto con una macro sin poner entre corchetes el bloque. Y poner el bloque entre corchetes ya hace que funcione de todos modos)

  • Personalmente, no me gustan estas propuestas, ya que hacen que |> (un operador que ya no le gusta) sea súper mágico.

Editar : como @StefanKarpinski señala a continuación , esto siempre es un cambio realmente importante.
Porque alguien podría depender de typeof(|>) <: Function .
Y estos cambios lo convertirían en un elemento de la sintaxis del lenguaje.

Opción adicional: nunca sucederá opción: agregue curry en todas partes # 554

Es demasiado tarde en el idioma para agregar curry.
Sería una locura romper, agregar enormes montones de ambigüedades por todas partes.
Y sé muy confuso.

Creo que con estas dos opciones básicamente cubre todo lo que vale la pena considerar.
No creo que se haya dicho nada más revelador (es decir, sin contar "+1 quiero esto"; o repeticiones de microvarientes en lo anterior).

Estoy bastante tentado a desaprobar |> en 0.7 para que luego podamos presentarlo con una semántica más útil y posiblemente no funcional que sospecho que es necesaria para que la tubería funcione bien.

Estoy bastante tentado a desaprobar |> en 0.7 para que luego podamos introducirlo con semánticas más útiles y posiblemente no funcionales que sospecho son necesarias para que la tubería funcione bien.

El único caso de ruptura en esa lista es cuando |> hace que su lado derecho finja estar curry.
Ya sea para insertar sus argumentos en la primera, o en la última posición (o es) del último argumento, o en alguna otra posición fija (la segunda podría tener sentido).
Sin usar _ como marcador para qué argumento insertar.

No se han hecho otras propuestas innovadoras que nadie en este hilo haya tomado vagamente en serio.
Me sorprendería si hay otras definiciones sensatas y aún rompedoras para esa operación,
que nadie ha sugerido todavía en estos últimos casi 4 años.

De todos modos, desaprobarlo no sería terrible.
Los paquetes que lo usan aún pueden tenerlo a través de uno de los paquetes de macros.

Otra idea podría ser mantener |> con el comportamiento actual e introducir la nueva funcionalidad con un nombre diferente que no requiera el uso de la tecla shift, como \\ (que no incluso analizar como operador en este momento). Hablamos de esto en Slack una vez, pero creo que la historia probablemente se haya perdido en las arenas del tiempo.

Las tuberías se utilizan a menudo de forma interactiva, y la facilidad de escribir para el operador afecta la "ligereza" que se siente al utilizarlas. Un solo carácter | podría ser bueno.

Un solo carácter | podría ser bueno.

Interactivamente sí, pero luego es suficiente tenerlo en .juliarc.jl (que tengo desde hace mucho tiempo ;-p)

que no requiere el uso de la tecla shift

Tenga en cuenta que esta es una propiedad que depende en gran medida de la configuración regional. Por ejemplo, mi teclado sueco ha enviado una serie de caracteres para cambiar y (bastante horribles) combinaciones AltGr para dejar espacio para otras tres letras.

¿Existe alguna tradición de usar |> para este propósito? [Mathematica] (http://reference.wolfram.com/language/guide/Syntax.html) tiene // para la aplicación de la función postfix, que debería ser fácil de escribir en la mayoría de los teclados y podría estar disponible, si ya no se usa para comentarios (como en C ++) o división de enteros (como en Python).

Algo que contenga | tiene la buena conexión con el script de shell, aunque si por supuesto, se esperaría que un solo | fuera bit a bit OR. ¿Se toma || como OR lógico? ¿Qué pasa con ||| ? Escribir tres veces un carácter difícil de alcanzar no es mucho más difícil que escribirlo una vez.

¿Existe alguna tradición de utilizar |> para este propósito?

Creo que la tradición de |> deriva de la familia de lenguajes ML. Cuando se trata de operadores, pocas comunidades de lenguajes de programación han explorado este espacio como lo ha hecho la comunidad ML / Haskell. Una pequeña selección de ejemplos:

Para agregar a la lista anterior, R usa%>%, y aunque ese lenguaje está anticuado, creo que su funcionalidad de tubería está muy bien diseñada. Una de las cosas que lo hace efectivo es la sintaxis de llaves, que permite escribir cosas como

x %>% { if(. < 5) { a(.) } else { b(.) } }

que sería un poco más detallado en Julia debido a su uso de declaraciones finales. Aunque mi ejemplo anterior es abstracto, mucha gente usa una sintaxis similar al escribir código de preprocesamiento de datos. Con cualquiera de las propuestas actuales en discusión, ¿se puede lograr algo similar a lo anterior, quizás mediante el uso de paréntesis?

También recomendaría el uso de un operador de tubería que dé alguna indicación visual de que los argumentos se están transmitiendo hacia adelante, como el símbolo > . Esto proporciona una pista útil para principiantes y aquellos que no están familiarizados con la programación funcional.

Aunque los usos propuestos de |> no son incompatibles sintácticamente con el uso típico actual, _son_ incompatibles con que |> sea ​​un operador, ya que la mayoría de ellos implican dar |> mucho más poder que una mera función infija. Incluso si estamos seguros de que queremos retener x |> f |> g para significar g(f(x)) , dejarlo como un operador normal probablemente excluirá cualquier mejora adicional. Si bien cambiar |> a una aplicación que no sea un operador y que realice una función de postfijo podría no interrumpir su uso _típico_ para la aplicación de función encadenada, aún no estaría permitido ya que rompería el uso _atípico_ de |> - cualquier que depende de que sea un operador. Romper el uso atípico todavía está rompiendo y, por lo tanto, no está permitido en las versiones 1.x. Si queremos hacer alguna de las propuestas anteriores con |> hasta donde yo sé, necesitamos hacer una sintaxis |> lugar de una función en 1.0.

@StefanKarpinski ¿Está creando una sintaxis |> lugar de una función incluso en la mesa en este momento? ¿Es posible ponerlo sobre la mesa a tiempo para tenerlo en su lugar para 1.0?

@StefanKarpinski ¿Está creando |> sintaxis en lugar de una función incluso en la mesa en este momento? ¿Es posible ponerlo sobre la mesa a tiempo para tenerlo en su lugar para 1.0?

Depreciarlo en 0.7 y eliminarlo directamente de 1.0 está sobre la mesa.
Luego tráigalo en algún momento durante 1.x como elemento de sintaxis.
Lo que en ese momento sería un cambio irreversible.

Alguien tendría que hacerlo, pero no creo que sea un cambio terriblemente difícil, así que sí, está sobre la mesa.

¿A qué se desaprobaría |> ? ¿Una implementación en Lazy.jl?

x |> f puede quedar obsoleto a f(x) .

¿Qué tal desaprobar l> pero al mismo tiempo introducir digamos ll> que tiene el mismo comportamiento que el actual l> ?

Si solo aceptamos la obsolescencia sin algún reemplazo, los paquetes que se basan en el comportamiento actual esencialmente se quedarían sin una buena opción. Si obtienen una expresión que es un poco menos agradable mientras tanto, pueden continuar con su diseño actual, pero aún dejamos la opción sobre la mesa para encontrar una solución realmente buena para l> en el futuro.

Esto afecta enormemente al ecosistema de Query and Friends: he creado un sistema que es bastante similar a la sintaxis de tubería en R tidyverse. Todo es bastante completo: cubre el archivo io para siete formatos de archivo tabulares actualmente (con dos más muy cercanos), todas las operaciones de consulta (como dplyr) y el trazado (no muy avanzado, pero soy optimista de que podemos tener algo que se sienta como ggplot pronto). Todo se basa en la implementación actual de l> ...

Debo decir que estoy a favor de mantener las opciones para algo mejor por l> sobre la mesa. Funciona bien para lo que he creado hasta ahora, pero podría ver fácilmente un enfoque mejor. Pero simplemente la desaprobación parece un paso muy radical que puede arruinar muchos paquetes.

La otra opción es hacer de x |> f una sintaxis alternativa para f(x) . Eso rompería el código que sobrecarga |> pero permitiría que el código que usa |> para el encadenamiento de funciones siga funcionando mientras deja las cosas abiertas para mejoras adicionales siempre que sean compatibles con eso.

La alternativa sería introducir una nueva sintaxis de encadenamiento sintáctico en el futuro, pero debe ser algo que actualmente sea un error de sintaxis, lo cual es una selección bastante pequeña en este momento.

La alternativa sería introducir una nueva sintaxis de encadenamiento sintáctico en el futuro, pero debe ser algo que actualmente sea un error de sintaxis, lo cual es una selección bastante pequeña en este momento.

¿Mi propuesta de arriba no lo permitiría? Es decir, haga |> un error de sintaxis en julia 1.0, y haga que ||> equivalente al |> . Para julia 1.0 esto sería una molestia menor para el código que actualmente usa |> porque uno tendría que cambiar a ||> . Pero creo que no sería tan malo, además de que podría automatizarse por completo. Luego, una vez que alguien tenga una buena idea por |> , puede reintroducirla en el idioma. En ese punto, habría ||> y |> alrededor, y supongo que ||> se desvanecerían lentamente en el fondo si todos comienzan a adoptar |> . Y luego, en un par de años, julia 2.0 podría eliminar ||> . En mi opinión, eso a) no causaría ningún problema real a nadie en el período de tiempo de julia 1.0, yb) dejaría todas las opciones sobre la mesa para una solución realmente buena por |> eventualmente.

|>(x, f) = f(x)
|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...) # tuple
|>(x, f, args...) = f(x, args...) # args

x = 1 |> (+, 1, 1) |> (-, 1) |> (*, 2) |> (/, 2) |> (+, 1) |> (*, 2) # tuple
y = 1 |> (+, 1, 1)... |> (-, 1)... |> (*, 2)... |> (/, 2)... |> (+, 1)... |> (*, 2)... # args

No es fácil escribir muchas veces, pero de izquierda a derecha y no usa macro.

function fibb_tuple(n)
    if n < 3
        return n
    end
    fibb_tuple(n-3) |> (+, fibb_tuple(n-2), fibb_tuple(n-1))
end

function fibb_args(n)
    if n < 3
        return n
    end
    fibb_args(n-3) |> (+, fibb_args(n-2), fibb_args(n-1))...
end

function fibb(n)
    if n < 3
        return n
    end
    fibb(n-3) + fibb(n-2) + fibb(n-1)
end

n = 25

println("fibb_tuple")
<strong i="8">@time</strong> fibb_tuple(1)
println("fibb_args")
<strong i="9">@time</strong> fibb_args(1)
println("fibb")
<strong i="10">@time</strong> fibb(1)

println("tuple")
<strong i="11">@time</strong> fibb_tuple(n)
println("args")
<strong i="12">@time</strong> fibb_args(n)
println("fibb")
<strong i="13">@time</strong> fibb(n)
fibb_tuple
  0.005693 seconds (2.40 k allocations: 135.065 KiB)
fibb_args
  0.003483 seconds (1.06 k allocations: 60.540 KiB)
fibb
  0.002716 seconds (641 allocations: 36.021 KiB)
tuple
  1.331350 seconds (5.41 M allocations: 151.247 MiB, 20.93% gc time)
args
  0.006768 seconds (5 allocations: 176 bytes)
fibb
  0.006165 seconds (5 allocations: 176 bytes)

|>(x, tuple::Tuple) = tuple[1](x, tuple[2:endof(tuple)]...) es horrible.
|>(x, f, args...) = f(x, args...) necesita más letras pero rápido.

Creo que permitir subject |> verb(_, objects) como una sintaxis como verb(subject, objects) significa admitir SVO (pero el valor predeterminado de Julia es VSO o VOS). Sin embargo, Julia apoya el mutltidipatch para que el sujeto pueda ser sujeto. Creo que deberíamos permitir una sintaxis similar a (subject1, subject2) |> verb(_, _, object1, object2) como verb(subject1, subject2, object1, object2) si introducimos la sintaxis SVO.
Es MIMO si se entiende como tubería, como @oxinabox .

¿Qué tal si usas (x)f como f(x) ?
(x)f(y) se puede leer como f(x)(y) y f(y)(x) así que elija evaluar primero a la derecha:

(x)f # f(x)
(x)f(y) # f(y)(x)
(x)f(y)(z) # f(y)(z)(x)
(x)(y)f(z) # f(z)(y)(x)
(a)(b)f(c)(d) # f(c)(d)(b)(a)
1(2(3, 4), 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
1 <| (2 <| (3, 4), 5 <| (6, 7), 8 <| (9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10)), but 2 <| (3, 4) == 2((3, 4)) so currently emit error
3 |> 2(_, 4) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3)2(_, 4))1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
(3, 4) |> 2(_, _) |> 1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))
((3, 4)2)1(_, 5(6, 7), 8(9, 10)) # 1(2(3, 4), 5(6, 7), 8(9, 10))

Esto puede manipular vararg claramente.
Pero romperá los operadores binarios sin espacios:

(a + b)+(c + d) # +(c + d)(a + b) == (c + d)(a + b): Error

Opción alternativa: agregue otro caso para la sintaxis de splatting. Tener f ... (x) desugar to (args ...) -> f (x, args ...)

Esto permitiría el currying sintácticamente ligero (manual):

#Basic example:
f(a,b,c,d) = #some definition
f...(a)(b,c,d) == f(a,b,c,d)
f...(a,b)(c,d) == f(a,b,c,d)
f...(a,b,c)(d) == f(a,b,c,d)
f...(a)...(b)(c,d) == f(a,b,c,d) # etc etc

# Use in pipelining:
x |> map...(f) |> g  |> filter...(h) |> sum

¿Cuándo dejas de curry? Las funciones de Julia no tienen aridad fija.

Julia ya tiene un operador de salpicaduras. Lo que propongo tendría exactamente el mismo comportamiento que el operador de splat actual.

I, e: f ... (x) == (args ...) -> f (x, args ...) es azúcar para hacer una lambda con splatting.

Esa definición siempre te da un objeto de función. Es de suponer que a veces quieres una respuesta.

Lo obtienes llamando explícitamente al objeto al final. Por ejemplo, observe la falta de ... antes del último conjunto de paréntesis en mi último ejemplo f ... (a) ... (b) (c, d) == f (a, b, c, d) .

También puede llamar al objeto de función devuelto con |>, lo que lo hace agradable para la canalización.

@saolof

Ejemplo básico:

f (a, b, c, d) = #alguna definición
f ... (a) (b, c, d) == f (a, b, c, d)
f ... (a, b) (c, d) == f (a, b, c, d)
f ... (a, b, c) (d) == f (a, b, c, d)
f ... (a) ... (b) (c, d) == f (a, b, c, d) # etc etc

Buena intuición para usar splat con encadenamiento de funciones pero demasiado complejo en mi sentido.
Hizo múltiples aplicaciones en un punto de la cadena.
En el encadenamiento de funciones, realiza una aplicación paso a paso a lo largo de la cadena.

Y @StefanKarpinski tiene razón, no sabes cuándo detenerte para aplicar funciones sobre sí mismas y finalmente aplicarlas a un elemento más escalar.

--(cortado)--

Lo siento, eso es lo bastante inútil e ilegible.
Vea mi segundo mensaje a continuación para obtener una explicación más clara (espero).

Dado lo funcional que ya es Julia, me gusta bastante la idea de @saolof de un operador de función curry. Realmente no entiendo de dónde vienen las objeciones semánticas, ya que parece que esto tiene una interpretación muy obvia.

Incluso puede crear un prototipo desde la comodidad de su propia respuesta:

ctranspose(f) = (a...) -> (b...) -> f(a..., b...)

map'(+)(1:10)

map'(+)'(1:10, 11:20)(21:30)

(+)'(1,2,3)(4,5)

1:10 |> map'(x->x^2) |> filter'(iseven)

Creo que tiene una sensación agradable.

Editar: Parece que esto también podría ser el camino para generalizar más esto. Si podemos escribir map∘(+, 1:10) entonces podemos escribir map∘(_, 1:10) para colocar primero el argumento curry, y el operador de curry determina el alcance de la lambda, resolviendo el mayor problema para este curry general.

Eh, eso es inteligente, @MikeInnes.

Me encanta cómo se muestra aquí la extrema extensibilidad de Julia. La solución unificadora para una amplia gama de requisitos para el encadenamiento de funciones resulta abusar de ctranspose ...

(aclaración: estoy obteniendo 1:10 |> map'(x->x^2) |> filter'(iseven) con esta propuesta, ¡así que estoy 💯% a favor!)

Para ser claros, no creo que debamos abusar del operador adjoint para esto, pero es una buena prueba de concepto que podemos tener una función concisa curry notación.

¿Quizás deberíamos introducir un nuevo operador Unicode? http://www.fileformat.info/info/unicode/char/1f35b/index.htm

(Lo siento...)

Siento que _'s siguen siendo una forma mucho más flexible de hacer lambdas

@bramtayl Creo que la idea en la edición de MikeInnes de su publicación es que los dos pueden coexistir: los subrayados estándar como en la solicitud de extracción de @stevengj funcionarían, el curry independiente como en la idea de Mike anterior funcionaría, y la combinación de los dos también funcionaría, permitiéndole usar el operador currying para delimitar el alcance de _ s dentro de él.

ah lo tengo

Eso lo hace no muy diferente de LazyCall.jl

En una nota más seria:

Para ser claros, no creo que debamos abusar del operador adjoint para esto

Probablemente una buena elección. Sin embargo, me gustaría expresar mis esperanzas de que si se implementa una solución de este tipo, se le proporcione un operador que sea fácil de escribir. La capacidad de hacer algo como 1:10 |> map'(x->x^2) es significativamente menos útil si cualquier carácter que reemplaza a ' requiere que lo busque en una tabla Unicode (o use un editor que admita expansiones LaTeX).

En lugar de abusar del operador adjunto, podríamos reutilizar el splat.

  • en un contexto de tubería (lineal)
  • adentro, en una llamada de función

    • haz salpicar antes que después

entonces

  • splat puede inducir un argumento de iterador faltante

Una especie de splat de alto orden, (con anacrusa si hay algún músico allí).
Esperando que no se agite demasiado el idioma.

EJEMPLO

1:10
    |> map(...x->x^2)
    |> filter(...iseven)

EJEMPLO 2

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      map(...i -> a*i/n) |>
      map(...t -> [r*cos(t), r*sin(t)]) 

podría representar

elmap = f -> (s -> map(f,s))

genpie = (r, a=2pi, n=12) ->
  (0:n-1) |>
      elmap(i -> a*i/n) |>
      elmap(t -> [r*cos(t), r*sin(t)]) 

No estoy seguro de si esto pertenece aquí, ya que la discusión ha evolucionado a un encadenamiento y sintaxis más avanzados / flexibles ... pero volviendo a la publicación inicial, el encadenamiento de funciones con sintaxis de puntos parece posible en este momento, con una pequeña configuración adicional. La sintaxis es solo una consecuencia de tener sintaxis de puntos para estructuras junto con funciones / cierres de primera clase.

mutable struct T
    move
    scale
    display
    x
    y
end

function move(x,y)
    t.x=x
    t.y=y
    return t
end
function scale(c)
    t.x*=c
    t.y*=c
    return t
end
function display()
    @printf("(%f,%f)\n",t.x,t.y)
end

function newT(x,y)
    T(move,scale,display,x,y)
end


julia> t=newT(0,0)
T(move, scale, display, 0, 0)

julia> t.move(1,2).scale(3).display()
(3.000000,6.000000)

La sintaxis parece muy similar a la POO convencional, con una peculiaridad de los "métodos de clase" que son mutables. No estoy seguro de cuáles son las implicaciones en el rendimiento.

@ivanctong Lo que ha descrito es algo más parecido a una interfaz fluida que al encadenamiento de funciones.

Dicho esto, resolver el problema del encadenamiento de funciones de manera más general tendría el beneficio adicional de ser también utilizable para interfaces fluidas. Si bien es ciertamente posible crear algo como una interfaz fluida utilizando miembros de estructura en Julia actualmente, me parece que va en gran medida en contra del espíritu y la estética del diseño de Julia.

La forma en que elixir lo hace donde el operador de tubería siempre pasa en el lado izquierdo como el primer argumento y permite argumentos adicionales después, ha sido bastante útil, me encantaría ver algo como "elixir" |> String.ends_with?("ixir") como ciudadano de primera clase en Julia.

Otros lenguajes lo definen como sintaxis uniforme de llamada a función .
Esta función ofrece varias ventajas (consulte Wikipedia), sería bueno que Julia la admitiera.

Entonces, ¿hay una interfaz fluida para Julia en este momento?

Envíe sus preguntas al foro de discusión del discurso de Julia .

En un ataque de piratería (¿¡y juicio cuestionable !?) He creado otra posible solución para la rigidez de la vinculación de marcadores de posición de función:

https://github.com/c42f/MagicUnderscores.jl

Como se señaló en https://github.com/JuliaLang/julia/pull/24990 , esto se basa en la observación de que a menudo uno quiere que ciertas ranuras de una función determinada enlacen una expresión de marcador _ posición MagicUnderscores hace que esto sea extensible para cualquier función definida por el usuario (muy en el espíritu de la maquinaria de transmisión). Por tanto, podemos tener cosas como

julia> <strong i="12">@_</strong> [1,2,3,4] |> filter(_>2, _)
2-element Array{Int64,1}:
 3
 4

julia> <strong i="13">@_</strong> [1,2,3,4] |> filter(_>2, _) |> length
2

"solo trabajo". (Con el @_ obviamente desapareciendo si es posible hacer de esto una solución general).

Alguna sugerencia de variación de @MikeInnes parecería adecuada para mis necesidades (generalmente largas cadenas de filtro, mapa, reducción, enumeración, zip, etc. usando la sintaxis do ).

c(f) = (a...) -> (b...) -> f(a..., b...)

1:10 |> c(map)() do x
    x^2
end |> c(filter)() do x
    x > 50
end

Esto funciona, aunque ya no puedo hacer que ' funcione. Es un poco más corto que:

1:10 |> x -> map(x) do x
    x^2
end |> x -> filter(x) do x
    x > 50
end

También supongo que uno podría hacer

cmap = c(map)
cfilter = c(filter)
cetc = c(etc)
...

1:10 |> cmap() do x
    x^2
end |> cfilter() do x
    x > 50
end |> cetc() do ...

A partir de la versión 1.0, deberá sobrecargar adjoint lugar de ctranspose . También puedes hacer:

julia> Base.getindex(f::Function, x...) = (y...) -> f(x..., y...)

julia> 1:10 |> map[x -> x^2] |> filter[x -> x>50]
3-element Array{Int64,1}:
  64
  81
 100

Si pudiéramos sobrecargar apply_type entonces podríamos obtener map{x -> x^2} :)

@MikeInnes , acabo de robar eso

Una contribución tardía y un poco frívola: ¿qué tal canalizar datos a cualquier ubicación en la lista de argumentos usando una combinación de operadores de curry izquierdo y derecho?

VERSION==v"0.6.2"
import Base: ctranspose, transpose  
ctranspose(f::Function) = (a...) -> ((b...) -> f(a..., b...))  
 transpose(f::Function) = (a...) -> ((b...) -> f(b..., a...))

"little" |> (*)'''("Mary ")("had ")("a ") |> (*).'(" lamb")

Clojure tiene algunas macros de subprocesamiento agradables. ¿Tenemos esos en el ecosistema de Julia en alguna parte?

Clojure tiene algunas macros de subprocesamiento agradables. ¿Tenemos esos en el ecosistema de Julia en alguna parte?

https://github.com/MikeInnes/Lazy.jl

Clojure tiene algunas macros de subprocesamiento agradables. ¿Tenemos esos en el ecosistema de Julia en alguna parte?

tenemos al menos 10 de ellos.
Publiqué una lista más arriba en el hilo.
https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539

¿Puedes editar la lista para tener LightQuery en lugar de los otros dos paquetes míos?

Dado que el operador |> proviene de elixir, ¿por qué no inspirarse en una de las formas que tienen para crear funciones anónimas?
en elixir puede usar &expr para definir una nueva función anónima y &n para capturar argumentos posicionales ( &1 son los primeros argumentos, &2 es el segundo, etc.)
En elixir hay cosas adicionales para escribir (por ejemplo, necesita un punto antes del paréntesis para llamar a una función anónima &(&1 + 1).(10) )

Pero así es como podría verse en julia

&(&1 * 10)        # same as: v -> v * 10
&(&2 + 2*&5)      # same as: (_, x, _, _, y) -> x + 2*y
&map(sqrt, &1)    # same as: v -> map(sqtr, v)

Entonces podemos usar el operador |> mejor

1:9 |> &map(&1) do x
  x^2
end |> &filter(&1) do x
  x in 25:50
end

en vez de

1:9 |> v -> map(v) do x
  x^2
end |> v -> filter(v) do x
  x in 25:50
end

tenga en cuenta que puede reemplazar las líneas 2 y 3 por .|> &(&1^2) o .|> (v -> v^2)

La principal diferencia con las proposiciones con marcador _ posición & delante de las expresiones hace que el alcance de los marcadores de posición sea obvio (para el lector y el compilador).

Tenga en cuenta que he tomado & en mis ejemplos, pero el uso de ? , _ , $ u otra cosa en su lugar, no cambiaría nada al caso.

Scala usa _ para el primer argumento, _ para el segundo argumento, etc., lo cual es conciso, pero rápidamente se quedan sin situaciones en las que puede aplicarlo (no se puede repetir ni revertir el orden de los argumentos). Tampoco tiene un prefijo ( & en la sugerencia anterior) que desambigua funciones de expresiones, y eso, en la práctica, es otro problema que impide su uso. Como practicante, terminas envolviendo las funciones en línea previstas entre paréntesis y llaves adicionales, con la esperanza de que sean reconocidas.

Entonces yo diría que la máxima prioridad al introducir una sintaxis como esta es que sea inequívoca.

Pero en cuanto a los prefijos para los argumentos, $ tiene una tradición en el mundo de los scripts de shell. Siempre es bueno usar personajes familiares. Si el |> es de Elixir, entonces ese podría ser un argumento para tomar & de Elixir también, con la idea de que los usuarios ya están pensando en ese modo. (Suponiendo que haya muchos ex usuarios de Elixir por ahí ...)

Una cosa que una sintaxis como esta probablemente nunca pueda capturar es crear una función que tome N argumentos pero use menos de N. El $1 , $2 , $3 en el cuerpo implica la existencia de 3 argumentos, pero si desea poner esto en una posición en la que se llamará con 4 argumentos (el último en ser ignorado), entonces no hay una forma natural de expresarlo. (Aparte de predefinir funciones de identidad para cada N y envolver la expresión con una de esas). Esto no es relevante para el caso motivador de ponerlo después de |> , que tiene solo un argumento.

Extendí el truco de Colon como si las funciones fueran matrices:

struct LazyCall{F} <: Function
    func::F
    args::Tuple
    kw::Dict
end

Base.getindex(f::Function,args...;kw...) = LazyCall{typeof(f)}(f,args,kw)

function (lf::LazyCall)(vals...; kwvals...)

    # keywords are free
    kw = merge(lf.kw, kwvals)

    # indices of free variables
    x_ = findall(x->isa(x,Colon),lf.args)
    # indices of fixed variables
    x! = setdiff(1:length(lf.args),x_)

    # the calling order is aligned with the empty spots
    xs = vcat(zip(x_,vals)...,zip(x!,lf.args[x!])...)
    args = map(x->x[2],sort(xs;by=x->x[1]))

    # unused vals go to the end
    callit = lf.func(args...,vals[length(x_)+1:end]...; kw...)

    return callit
end

[1,2,3,4,1,1,5]|> replace![ : , 1=>10, 3=>300, count=2]|> filter[>(50)]  # == [300]

log[2](2) == log[:,2](2) == log[2][2]() == log[2,2]()  # == true

Es mucho más lento que las lambdas o las macros de enhebrado, pero creo que es genial: p

Para recordarle a las personas que comentan aquí, eche un vistazo a la discusión relevante en https://github.com/JuliaLang/julia/pull/24990.

Además, te animo a que pruebes https://github.com/c42f/Underscores.jl, que ofrece una implementación compatible con el encadenamiento de funciones de la sintaxis _ para marcadores de posición. @jpivarski basado en sus ejemplos, puede que le resulte bastante familiar y cómodo.

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