Modismos de composición de función de Haskell (.) Y aplicación de función ($): uso correcto

129

He estado leyendo Real World Haskell , y me estoy acercando al final, pero una cuestión de estilo me ha estado fastidiando con los operadores (.)y ($).

Cuando escribe una función que es una composición de otras funciones, la escribe así:

f = g . h

Pero cuando aplicas algo al final de esas funciones, lo escribo así:

k = a $ b $ c $ value

Pero el libro lo escribiría así:

k = a . b . c $ value

Ahora, para mí se ven funcionalmente equivalentes, hacen exactamente lo mismo en mis ojos. Sin embargo, cuanto más miro, más veo a las personas que escriben sus funciones de la manera en que lo hace el libro: componer (.)primero y luego solo al final usar ($)para agregar un valor para evaluar el lote (nadie lo hace con muchas composiciones en dólares) .

¿Hay alguna razón para usar los libros que sea mucho mejor que usar todos los ($)símbolos? ¿O hay alguna mejor práctica aquí que no estoy obteniendo? ¿O es superfluo y no debería preocuparme en absoluto?

Robert Massaioli
fuente
44
Tenga en cuenta que el segundo ejemplo se puede hacer comok = a $ b $ c value
Thomas Eding
1
Sí, puede, como Zifre menciona a continuación, pero decidí dejarlo allí ya que no daña nada y hace que sus (y ahora sus) comentarios tengan sentido. Gracias de cualquier forma. +1 :)
Robert Massaioli
2
Otro enfoque común es a . b $ c valueque no me gusta tanto como el tercer ejemplo, pero guarda algunos caracteres.
John L

Respuestas:

153

Creo que puedo responder esto desde la autoridad.

¿Hay alguna razón para usar la forma de los libros que sea mucho mejor que usar todos los símbolos ($)?

No hay una razón especial. Bryan y yo preferimos reducir el ruido de la línea. .es más silencioso que $. Como resultado, el libro usa la f . g . h $ xsintaxis.

Don Stewart
fuente
3
Bueno, no puedo esperar una mejor respuesta que esta; de uno de los autores mismos. :) Y eso tiene sentido, se ve mucho más tranquilo en la página mientras leo. Marcar esto como la respuesta porque responde directamente a la pregunta. Gracias por la respuesta; y, de hecho, el libro.
Robert Massaioli
38
No estoy en desacuerdo con el autor, pero creo que hay otra razón más destacada en el modelo mental de crear algo en lugar de usarlo . Los usuarios de Haskell tienden a querer pensar más bien f.g.hcomo una nueva creación inteligente f(g(h())). Ahora están llamando a una nueva función, aunque anónima, que crearon en lugar de simplemente encadenar un gran diccionario de llamadas a funciones prefabricadas como un usuario de PHP.
Evan Carroll
1
Esa es una declaración interesante: 'reducir el ruido de la línea' y 'más silencioso que'. Nunca pensé en los lenguajes de programación en esta terminología, pero tiene mucho sentido.
Rabarberski
53

De hecho, son equivalentes: tenga en cuenta que el $operador no hace, esencialmente, nada. f $ xevalúa a f x. El propósito de $es su comportamiento de fijación: derecho-asociativo y precedencia mínima. Al eliminar $y usar paréntesis para agrupar en lugar de precedencia infija, los fragmentos de código se ven así:

k = a (b (c (value)))

y

k = (a . b . c) value

La razón para preferir la .versión sobre la $versión es la misma razón para preferir ambas sobre la versión entre paréntesis anterior: atractivo estético.

Sin embargo, algunos podrían preguntarse si el uso de operadores de infijo en lugar de paréntesis se basa en una necesidad subconsciente de evitar cualquier posible parecido con Lisp (es broma ... ¿creo?).

CA McCann
fuente
38

Agregaría eso f . g $ x, f . ges una unidad sintáctica significativa.

Mientras tanto, en f $ g $ x, f $ gno es una unidad significativa. Una cadena de $es posiblemente más imperativa: primero obtenga el resultado gde x, luego hágalo f, luego hágalo foo, luego etc.

Mientras tanto, una cadena de .es posiblemente más declarativa y, en cierto sentido, más cercana a una vista centrada en el flujo de datos: componga una serie de funciones y, en última instancia, las aplique a algo.

sclv
fuente
2
Estoy aprendiendo Haskell (no he codificado nada significativo) pero me gusta pensar en $ como una especie de operador de "tubería". Es muy parecido a la terminal | pipe (excepto que los datos fluyen de derecha a izquierda), pero al igual que los procesos terminales, cada unidad es explícitamente independiente.
LostSalad
1
El $operador parece comportarse de manera similar al <|operador en F #. Creo que ambos se definen igual, de hecho, en F # (<|) a b = a by en Haskell a $ b = a b, que son esencialmente equivalentes.
Tom Galvin
@Quackmatic Bastante seguro de que está (<|) b a = a ben F #, ya que se usa como [1; 2; 3; 4; 5] <| List.map ((+) 1), si la memoria sirve.
BalinKingOfMoria Reinstale CMs
@BalinKingOfMoria El <|operador pasa el valor a la función, en lugar de pasar la función al valor; en su lugar, su ejemplo de código funcionaría con el |>operador.
Tom Galvin
@Quackmatic Oh. No sabía que había varias versiones (no he usado mucho F #).
BalinKingOfMoria Reinstale CMs
18

Para mí, creo que la respuesta es (a) la pulcritud, como dijo Don ; y (b) encuentro que cuando estoy editando código, mi función puede terminar en un estilo libre de puntos, y luego todo lo que tengo que hacer es eliminar el último en $lugar de regresar y cambiar todo. Un punto menor, sin duda, pero agradable.

Antal Spector-Zabusky
fuente
Sí, esa es una buena razón para escribirlo así, si quieres terminar en una composición de función, entonces es más rápido. :) Yay para guardar las pulsaciones de teclado, pero lo más importante, el tiempo. +1
Robert Massaioli
13

Hay una discusión interesante de esta pregunta en este hilo de Haskell-Cafe . Aparentemente, hay un punto de vista minoritario que sostiene que la asociatividad correcta $es "simplemente errónea" , y elegir f . g . h $ xsobre f $ g $ h $ xes una forma de esquivar el problema.

Travis Brown
fuente
Oye, encontraste una discusión completa sobre el asunto. Eso es increíble, estoy leyendo ahora. +1
Robert Massaioli
2
Argumentos similares aquí: ghc.haskell.org/trac/haskell-prime/wiki/…
enrique
Tenga en cuenta que $!también tiene la asociatividad correcta "claramente equivocada" - ¿Por qué es $ operador derecho-asociativo? .
imz - Ivan Zakharyaschev
3

Es solo una cuestión de estilo. Sin embargo, la forma en que lo hace el libro tiene más sentido para mí. Compone todas las funciones y luego lo aplica al valor.

Su método se ve extraño, y el último $es innecesario.

Sin embargo, realmente no importa. En Haskell, generalmente hay muchas, muchas formas correctas de hacer lo mismo.

Zifre
fuente
3
No me di cuenta de que los últimos $ eran innecesarios, pero debería haberlo hecho. Gracias y lo dejaré allí para que la gente sepa lo que significa este comentario.
Robert Massaioli
0

Me doy cuenta de que esta es una pregunta muy antigua, pero creo que hay otra razón para esto que no se ha mencionado.

Si está declarando una nueva función sin puntos f . g . h, el valor que pase se aplicará automáticamente. Sin embargo, si escribe f $ g $ h, no funcionará.

Creo que la razón por la que el autor prefiere el método de composición es porque lleva a una buena práctica de desarrollar funciones.

hackeryarn
fuente