Composición de Haskell (.) Vs operador de avance de tubería de F # (|>)


100

En F #, el uso del operador pipe-forward`` |>es bastante común. Sin embargo, en Haskell solo he visto cómo (.)se usa la composición de funciones,. Entiendo que están relacionados , pero ¿hay alguna razón lingüística por la que no se use pipe-forward en Haskell o es algo más?


2
La respuesta de Lihanseys afirma que &es de Haskell |>. Enterrado en lo profundo de este hilo y me tomó unos días descubrirlo. Lo uso mucho, porque naturalmente lees de izquierda a derecha para seguir tu código.
itmuckel

Respuestas:


61

Estoy siendo un poco especulativo ...

Cultura : Creo que |>es un operador importante en la "cultura" de F #, y tal vez de manera similar con .Haskell. F # tiene un operador de composición de funciones, <<pero creo que la comunidad F # tiende a usar menos el estilo sin puntos que la comunidad Haskell.

Diferencias de idioma : no sé lo suficiente sobre ambos idiomas para comparar, pero quizás las reglas para generalizar los enlaces let son lo suficientemente diferentes como para afectar esto. Por ejemplo, sé que en F # a veces escribo

let f = exp

no se compilará, y necesita una conversión eta explícita:

let f x = (exp) x   // or x |> exp

para que se compile. Esto también aleja a la gente del estilo libre de puntos / compositivo y hacia el estilo de canalización. Además, la inferencia de tipo F # a veces exige canalización, por lo que un tipo conocido aparece a la izquierda (ver aquí ).

(Personalmente, encuentro ilegible el estilo sin puntos, pero supongo que cada cosa nueva / diferente parece ilegible hasta que te acostumbras).

Creo que ambos son potencialmente viables en cualquier idioma, y ​​la historia / cultura / accidente puede definir por qué cada comunidad se estableció en un "atractor" diferente.


7
Estoy de acuerdo con las diferencias culturales. Haskell tradicional hace uso de .y $, por lo que la gente continúa usándolos.
Amok

9
Point free es a veces más legible que significativo, a veces menos. Por lo general, lo uso en el argumento de funciones como mapa y filtro, para evitar que una lambda desordene las cosas. A veces también lo uso en funciones de nivel superior, pero con menos frecuencia y solo cuando es algo sencillo.
Paul Johnson

2
No veo mucha cultura en él, en el sentido de que simplemente no hay muchas opciones en el asunto en lo que respecta a F # (por las razones que tú y Ganesh mencionan). Entonces diría que ambos son viables en Haskell, pero F # definitivamente está mucho mejor equipado para usar el operador de tubería.
Kurt Schelfthout

2
sí, la cultura de la lectura de izquierda a derecha :) incluso las matemáticas deberían enseñarse de esa manera ...
nicolas

1
@nicolas de izquierda a derecha es una elección arbitraria. Puedes acostumbrarte de derecha a izquierda.
Martin Capodici

86

En F # (|>)es importante debido a la verificación de tipo de izquierda a derecha. Por ejemplo:

List.map (fun x -> x.Value) xs

generalmente no se verificará el tipo, porque incluso si xsse conoce el tipo de, el tipo de argumento xde la lambda no se conoce en el momento en que el verificador de tipos lo ve, por lo que no sabe cómo resolverlo x.Value.

A diferencia de

xs |> List.map (fun x -> x.Value)

funcionará bien, porque el tipo de xsconducirá al tipo de xser conocido.

Se requiere la verificación de tipo de izquierda a derecha debido a la resolución de nombres involucrada en construcciones como x.Value. Simon Peyton Jones ha escrito una propuesta para agregar un tipo similar de resolución de nombres a Haskell, pero sugiere usar restricciones locales para rastrear si un tipo admite una operación en particular o no. Entonces, en la primera muestra, el requisito que xnecesita una Valuepropiedad se trasladaría hasta que xsse vea y este requisito se pueda resolver. Sin embargo, esto complica el sistema de tipos.


1
Lo interesante es que hay un operador (<|) similar a (.) En Haskell con la misma dirección de datos de derecha a izquierda. Pero, ¿cómo funcionará la resolución de tipos para ello?
The_Ghost

7
(<|) es en realidad similar al ($) de Haskell. La verificación de tipo de izquierda a derecha solo es necesaria para resolver cosas como .Value, por lo que (<|) funciona bien en otros escenarios, o si usa anotaciones de tipo explícitas.
GS - Discúlpate con Monica

44

Más especulaciones, esta vez del lado predominantemente de Haskell ...

($)es el cambio (|>), y su uso es bastante común cuando no se puede escribir código sin puntos. Entonces, la razón principal por la que (|>)no se usa en Haskell es que su lugar ya está ocupado ($).

Además, hablando de un poco de experiencia en F #, creo que (|>)es muy popular en el código F # porque se parece a la Subject.Verb(Object)estructura de OO. Dado que F # tiene como objetivo una integración funcional / OO sin problemas, Subject |> Verb Objectes una transición bastante suave para los nuevos programadores funcionales.

Personalmente, también me gusta pensar de izquierda a derecha, así que lo uso (|>)en Haskell, pero no creo que muchas otras personas lo hagan.


Hola Nathan, ¿"flip ($)" está predefinido en algún lugar de la plataforma Haskell? El nombre "(|>)" ya está definido en Data.Sequence con otro significado. Si aún no está definido, ¿cómo lo llama? Estoy pensando en ir con "($>) = flip ($)"
mattbh

2
@mattbh: No es que pueda encontrar con Hoogle. No lo sabía Data.Sequence.|>, pero $>parece razonable para evitar conflictos allí. Honestamente, solo hay una cantidad limitada de operadores atractivos, por lo que solo usaría |>para ambos y manejaría los conflictos caso por caso. (También estaría tentado a usar un alias Data.Sequence.|>como snoc)
Nathan Shively-Sanders

2
($)y (|>)son aplicación, no composición. Los dos están relacionados (como señala la pregunta) pero no son lo mismo (el suyo fces (Control.Arrow.>>>)para funciones).
Nathan Shively-Sanders

11
En la práctica, F # |>me recuerda |más a UNIX que a cualquier otra cosa.
Kevin Cantu

3
Otro beneficio de |>F # es que tiene buenas propiedades para IntelliSense en Visual Studio. Escriba |>y obtendrá una lista de funciones que se pueden aplicar al valor de la izquierda, similar a lo que sucede al escribir .después de un objeto.
Kristopher Johnson

32

Creo que estamos confundiendo las cosas. ( .) De Haskell es equivalente a ( ) de F # >>. No confundir con F # 's ( |>) que es solo una aplicación de función invertida y es como Haskell's ( $) - invertida:

let (>>) f g x = g (f x)
let (|>) x f = f x

Creo que los programadores de Haskell lo usan con $frecuencia. Quizás no tan a menudo como los programadores de F # tienden a usar |>. Por otro lado, algunos tipos de F # usan >>en un grado ridículo: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx


2
como dice, es como el $operador de Haskell : al revés, también puede definirlo fácilmente como: a |> b = flip ($)que se convierte en equivalente a la tubería de F #, por ejemplo, puede hacerlo[1..10] |> map f
vis

8
Creo que ( .) es lo mismo que ( <<), mientras que ( >>) es la composición inversa. Eso es ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3vs( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
Dobes Vandermeer

-1 para "usar >> en un grado ridículo". (Bueno, no lo hice, pero entendiste mi punto). F en F # es para "funcional", por lo que la composición de funciones es legítima.
Florian F

1
Ciertamente estoy de acuerdo en que la composición de funciones es legítima. ¡Por "algunos chicos F #" me refiero a mí mismo! Ese es mi propio blog. : P
AshleyF

No creo que .sea ​​equivalente a >>. No sé si F # lo ha hecho, <<pero ese sería el equivalente (como en Elm).
Matt Joiner

28

Si desea usar F # |>en Haskell, entonces en Data.Function es el &operador (desde base 4.8.0.0).


Cualquier razón de Haskell para elegir &más |>? Siento que |>es mucho más intuitivo y también me recuerda al operador de tubería de Unix.
yeshengm

16

Composición de izquierda a derecha en Haskell

Algunas personas también usan el estilo de izquierda a derecha (paso de mensajes) en Haskell. Consulte, por ejemplo, la biblioteca mps en Hackage. Un ejemplo:

euler_1 = ( [3,6..999] ++ [5,10..999] ).unique.sum

Creo que este estilo se ve bien en algunas situaciones, pero es más difícil de leer (es necesario conocer la biblioteca y todos sus operadores, el redefinido también (.)es perturbador).

También hay operadores de composición de izquierda a derecha y de derecha a izquierda en Control.Category , parte del paquete base. Compare >>>y <<<respectivamente:

ghci> :m + Control.Category
ghci> let f = (+2) ; g = (*3) in map ($1) [f >>> g, f <<< g]
[9,5]

Hay una buena razón para preferir la composición de izquierda a derecha a veces: el orden de evaluación sigue el orden de lectura.


Bien, entonces (>>>) es en gran medida equiparable a (|>)?
Khanzor

@Khanzor No exactamente. (|>) aplica un argumento, (>>>) es principalmente composición de funciones (o cosas similares). Entonces supongo que hay alguna diferencia de fijeza (no lo comprobé).
Sastanin

15

He visto >>>que me utilizan flip (.), y a menudo lo uso yo mismo, especialmente para cadenas largas que se entienden mejor de izquierda a derecha.

>>> es en realidad de Control.Arrow y funciona en más que solo funciones.


>>>se define en Control.Category.
Steven Shaw

13

Aparte del estilo y la cultura, esto se reduce a optimizar el diseño del lenguaje para código puro o impuro.

El |>operador es común en F # en gran parte porque ayuda a ocultar dos limitaciones que aparecen con código predominantemente impuro:

  • Inferencia de tipo de izquierda a derecha sin subtipos estructurales.
  • La restricción de valor.

Tenga en cuenta que la primera limitación no existe en OCaml porque el subtipo es estructural en lugar de nominal, por lo que el tipo estructural se refina fácilmente mediante unificación a medida que avanza la inferencia de tipos.

Haskell toma una compensación diferente, eligiendo enfocarse en un código predominantemente puro donde estas limitaciones se pueden eliminar.


8

Creo que el operador de avance de tubería de F # ( |>) debería frente a ( & ) en haskell.

// pipe operator example in haskell

factorial :: (Eq a, Num a) =>  a -> a
factorial x =
  case x of
    1 -> 1
    _ -> x * factorial (x-1)

// terminal
ghic >> 5 & factorial & show

Si no le gusta el &operador ( ), puede personalizarlo como F # o Elixir:

(|>) :: a -> (a -> b) -> b
(|>) x f = f x
infixl 1 |>
ghci>> 5 |> factorial |> show

¿Por qué infixl 1 |>? Ver el documento en función de datos (&)

infixl = infijo + asociatividad izquierda

infixr = infix + asociatividad derecha


(.)

( .) significa composición de funciones. Significa (fg) (x) = f (g (x)) en matemáticas.

foo = negate . (*3)
// ouput -3
ghci>> foo 1
// ouput -15
ghci>> foo 5

es igual

// (1)
foo x = negate (x * 3) 

o

// (2)
foo x = negate $ x * 3 

El $operador ( ) también se define en la función de datos ($) .

( .) se utiliza para crear Hight Order Functiono closure in js. Ver ejemplo:


// (1) use lamda expression to create a Hight Order Function
ghci> map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]


// (2) use . operator to create a Hight Order Function
ghci> map (negate . abs) [5,-3,-6,7,-3,2,-19,24]  
[-5,-3,-6,-7,-3,-2,-19,-24]

Vaya, Menos (código) es mejor.


Comparar |>y.

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

Es la diferencia entre left —> righty right —> left. ⊙﹏⊙ |||

Humanización

|>y &es mejor que.

porque

ghci> sum (replicate 5 (max 6.7 8.9))

// equals

ghci> 8.9 & max 6.7 & replicate 5 & sum

// equals

ghci> 8.9 |> max 6.7 |> replicate 5 |> sum

// equals

ghci> (sum . replicate 5 . max 6.7) 8.9

// equals

ghci> sum . replicate 5 . max 6.7 $ 8.9

¿Cómo programar funcional en lenguaje orientado a objetos?

visite http://reactivex.io/

Soporte de TI :

  • Java: RxJava
  • JavaScript: RxJS
  • C #: Rx.NET
  • C # (Unidad): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C ++: RxCpp
  • Lua: RxLua
  • Ruby: Rx.rb
  • Python: RxPY
  • Ir: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Kotlin: RxKotlin
  • Rápido: RxSwift
  • PHP: RxPHP
  • Elixir: reactivo
  • Dardo: RxDart

1

Este es mi primer día para probar Haskell (después de Rust y F #), y pude definir el operador |> de F #:

(|>) :: a -> (a -> b) -> b
(|>) x f = f x
infixl 0 |>

Y parece funcionar:

factorial x =
  case x of
    1 -> 1
    _ -> x * factorial (x-1)

main =     
    5 |> factorial |> print

Apuesto a que un experto en Haskell puede ofrecerle una solución aún mejor.


1
también puede definir operadores infijos infix :) x |> f = f x
Jamie
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.