Composizione Haskell (.) Vs operatore pipe forward di F # (|>)


100

In F #, l'uso dell'operatore pipe-forward,, |>è piuttosto comune. Tuttavia, in Haskell ho visto solo la composizione di funzioni,, (.)utilizzata. Capisco che siano correlati , ma c'è una ragione linguistica per cui pipe-forward non viene utilizzato in Haskell o è qualcos'altro?


2
La risposta di Lihanseys afferma che &è di Haskell |>. Sepolto in profondità in questo filo e mi ci sono voluti alcuni giorni per scoprirlo. Lo uso molto, perché leggi naturalmente da sinistra a destra per seguire il tuo codice.
itmuckel

Risposte:


61

Sono un po 'speculativo ...

Cultura : penso |>sia un operatore importante nella "cultura" di F #, e forse in modo simile .per Haskell. F # ha un operatore di composizione delle funzioni, <<ma penso che la comunità di F # tenda a usare lo stile senza punti meno della comunità Haskell.

Differenze linguistiche : non so abbastanza su entrambe le lingue per confrontarle, ma forse le regole per generalizzare i let-binding sono sufficientemente diverse da influire su questo. Ad esempio, so che a volte in F # scrivo

let f = exp

non verrà compilato e avrai bisogno di una conversione eta esplicita:

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

per farlo compilare. Questo allontana anche le persone dallo stile compositivo / senza punti e verso lo stile di pipeline. Inoltre, l'inferenza del tipo F # a volte richiede il pipelining, in modo che un tipo noto venga visualizzato a sinistra (vedere qui ).

(Personalmente, trovo illeggibile lo stile senza punti, ma suppongo che ogni cosa nuova / diversa sembri illeggibile finché non ci si abitua.)

Penso che entrambi siano potenzialmente praticabili in entrambe le lingue, e la storia / cultura / incidente può definire il motivo per cui ogni comunità si è stabilita in un diverso "attrattore".


7
Sono d'accordo con le differenze culturali. Haskell tradizionale utilizza .e $, quindi le persone continuano a usarli.
Amok

9
Point free a volte è più leggibile di pointful, a volte meno. Generalmente lo uso nell'argomento di funzioni come map e filter, per evitare di avere un lambda che ingombra le cose. A volte lo uso anche nelle funzioni di primo livello, ma meno spesso e solo quando è qualcosa di semplice.
Paul Johnson,

2
Non ci vedo molta cultura, nel senso che semplicemente non c'è molta scelta in materia per quanto riguarda F # (per le ragioni menzionate da te e Ganesh). Quindi direi che entrambi sono utilizzabili in Haskell, ma F # è decisamente molto meglio attrezzato per l'utilizzo dell'operatore di pipeline.
Kurt Schelfthout

2
sì, la cultura della lettura da sinistra a destra :) anche la matematica dovrebbe essere insegnata in questo modo ...
nicolas

1
@nicolas da sinistra a destra è una scelta arbitraria. Puoi abituarti da destra a sinistra.
Martin Capodici

86

In F # (|>)è importante a causa del controllo del tipo da sinistra a destra. Per esempio:

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

generalmente non esegue il typecheck, perché anche se il tipo di xsè noto, il tipo dell'argomento xdel lambda non è noto nel momento in cui il typechecker lo vede, quindi non sa come risolvere x.Value.

In contrasto

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

funzionerà bene, perché il tipo di xsporterà al tipo di xessere conosciuto.

Il controllo del tipo da sinistra a destra è richiesto a causa della risoluzione del nome coinvolta in costrutti come x.Value. Simon Peyton Jones ha scritto una proposta per aggiungere un tipo simile di risoluzione dei nomi a Haskell, ma suggerisce di utilizzare i vincoli locali per tracciare se un tipo supporta o meno una particolare operazione. Quindi nel primo campione il requisito che xnecessita di una Valueproprietà sarebbe stato portato avanti fino a quando non xsfosse stato visto e questo requisito potrebbe essere risolto. Tuttavia, questo complica il sistema dei tipi.


1
È interessante notare che esiste un operatore (<|) simile a (.) In Haskell con la stessa direzione dei dati da destra a sinistra. Ma come funzionerà la risoluzione del tipo per questo?
The_Ghost

7
(<|) è in realtà simile a ($) di Haskell. Il controllo del tipo da sinistra a destra è richiesto solo per risolvere cose come .Value, quindi (<|) funziona bene in altri scenari o se si utilizzano annotazioni di tipo esplicite.
GS - Chiedo scusa a Monica

44

Altre speculazioni, questa volta dal lato prevalentemente Haskell ...

($)è il capovolgimento di (|>), e il suo utilizzo è abbastanza comune quando non è possibile scrivere codice senza punti. Quindi il motivo principale che (|>)non viene utilizzato in Haskell è che il suo posto è già stato preso($) .

Inoltre, parlando di un po 'di esperienza F #, penso che (|>)sia così popolare nel codice F # perché assomiglia alla Subject.Verb(Object)struttura di OO. Poiché F # mira a un'integrazione funzionale / OO uniforme, Subject |> Verb Objectè una transizione piuttosto fluida per i nuovi programmatori funzionali.

Personalmente, mi piace anche pensare da sinistra a destra, quindi uso (|>)in Haskell, ma non credo che molte altre persone lo facciano.


Ciao Nathan, "flip ($)" è predefinito ovunque nella piattaforma Haskell? Il nome "(|>)" è già definito in Data.Sequence con un altro significato. Se non è già definito, come lo chiami? Sto pensando di andare con "($>) = flip ($)"
mattbh

2
@ mattbh: Non che riesco a trovare con Hoogle. Non sapevo Data.Sequence.|>, ma $>sembra ragionevole evitare conflitti lì. Onestamente, ci sono solo così tanti operatori di bell'aspetto, quindi userei solo |>per entrambi e gestirò i conflitti caso per caso. (Inoltre sarei tentato di fare solo uno pseudonimo Data.Sequence.|>come snoc)
Nathan Shively-Sanders,

2
($)e (|>)sono applicazione non composizione. I due sono correlati (come nota la domanda) ma non sono la stessa cosa (il tuo fcè (Control.Arrow.>>>)per le funzioni).
Nathan Shively-Sanders

11
In pratica, F # in |>realtà mi ricorda UNIX |più di ogni altra cosa.
Kevin Cantu

3
Un altro vantaggio di |>in F # è che ha proprietà interessanti per IntelliSense in Visual Studio. Digita |>e ottieni un elenco di funzioni che possono essere applicate al valore a sinistra, simile a quello che accade quando si digita .dopo un oggetto.
Kristopher Johnson

32

Penso che stiamo confondendo le cose. Haskell's ( .) è equivalente a F # ( >>). Da non confondere con F # 's ( |>) che è solo un'applicazione funzione invertita ed è come Haskell ( $) - invertita:

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

Credo che i programmatori Haskell usino $spesso. Forse non così spesso come i programmatori F # tendono a usare |>. D'altra parte, alcuni ragazzi di F # usano >>in misura ridicola: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx


2
come dici tu è come l' $operatore di Haskell - invertito, puoi anche facilmente definirlo come: a |> b = flip ($)che diventa equivalente alla pipeline di F #, ad esempio puoi farlo[1..10] |> map f
vis

8
Penso che ( .) sia uguale a ( <<), mentre ( >>) è la composizione inversa. Questo è il ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3vs( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
Dobes Vandermeer

-1 per "usa >> in misura ridicola". (Beh, non l'ho fatto ma hai capito il mio punto). F in F # sta per "funzionale", quindi la composizione della funzione è legittima.
Florian F

1
Certamente sono d'accordo che la composizione delle funzioni sia legittima. Con "alcuni ragazzi in F #" mi riferisco a me stesso! Quello è il mio blog. : P
AshleyF

Non credo .sia equivalente a >>. Non so se F # ha <<ma sarebbe l'equivalente (come in Elm).
Matt Joiner

28

Se vuoi usare F # |>in Haskell, allora in Data.Function c'è l' &operatore (da base 4.8.0.0).


Qual è il motivo per Haskell scegliere &sopra |>? Mi sembra che |>sia molto più intuitivo e mi ricorda anche l'operatore pipe di Unix.
yeshengm

16

Composizione da sinistra a destra a Haskell

Alcune persone usano lo stile da sinistra a destra (passaggio di messaggi) anche in Haskell. Vedi, per esempio, mps biblioteca Hackage. Un esempio:

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

Penso che questo stile sia carino in alcune situazioni, ma è più difficile da leggere (bisogna conoscere la libreria e tutti i suoi operatori, anche la ridefinizione (.)disturba).

Ci sono anche operatori di composizione da sinistra a destra e da destra a sinistra in Control.Category , parte del pacchetto base. Confronta >>>e <<<rispettivamente:

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

A volte c'è una buona ragione per preferire la composizione da sinistra a destra: l'ordine di valutazione segue l'ordine di lettura.


Bello, quindi (>>>) è in gran parte equiparabile a (|>)?
Khanzor

@ Khanzor Non esattamente. (|>) applica un argomento, (>>>) è principalmente composizione di funzioni (o cose simili). Quindi suppongo che ci sia qualche differenza di fissità (non l'ho controllata).
sastanin

15

Ho visto >>>essere usato perflip (.) , e spesso lo uso io stesso, specialmente per catene lunghe che sono meglio comprese da sinistra a destra.

>>> è in realtà da Control.Arrow e funziona su più di semplici funzioni.


>>>è definito in Control.Category.
Steven Shaw,

13

A parte lo stile e la cultura, questo si riduce all'ottimizzazione del design del linguaggio per il codice puro o impuro.

L' |>operatore è comune in F # in gran parte perché aiuta a nascondere due limitazioni che appaiono con codice prevalentemente impuro:

  • Inferenza di tipo da sinistra a destra senza sottotipi strutturali.
  • La restrizione del valore.

Si noti che la prima limitazione non esiste in OCaml perché il sottotipo è strutturale anziché nominale, quindi il tipo strutturale è facilmente raffinato tramite l'unificazione man mano che l'inferenza del tipo progredisce.

Haskell prende un compromesso diverso, scegliendo di concentrarsi su un codice prevalentemente puro in cui queste limitazioni possono essere eliminate.


8

Penso che l'operatore pipe forward ( |>) di F # dovrebbe vs ( & ) in 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

Se non ti piace l' &operatore ( ), puoi personalizzarlo come F # o Elixir:

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

Perché infixl 1 |>? Vedi il documento in Data-Function (&)

infixl = infix + associatività sinistra

infixr = infix + associatività a destra


(.)

( .) significa composizione della funzione. Significa (fg) (x) = f (g (x)) in matematica.

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

è uguale

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

o

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

L' $operatore ( ) è definito anche in Funzione dati ($) .

( .) viene utilizzato per creare Hight Order Functiono closure in js. Vedi esempio:


// (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]

Wow, Less (code) è meglio.


Confronta |>e.

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

È la differenza tra left —> righte right —> left. ⊙﹏⊙ |||

umanizzazione

|>ed &è meglio di.

perché

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

Come si fa la programmazione funzionale in linguaggio orientato agli oggetti?

visitare http://reactivex.io/

Supporto IT :

  • Java: RxJava
  • JavaScript: RxJS
  • C #: Rx.NET
  • C # (Unity): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C ++: RxCpp
  • Lua: RxLua
  • Rubino: Rx.rb
  • Python: RxPY
  • Vai: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Kotlin: RxKotlin
  • Swift: RxSwift
  • PHP: RxPHP
  • Elisir: reattivo
  • Dart: RxDart

1

Questo è il mio primo giorno per provare Haskell (dopo Rust e F #) e sono stato in grado di definire l'operatore |> di F #:

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

e sembra funzionare:

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

main =     
    5 |> factorial |> print

Scommetto che un esperto Haskell può darti una soluzione ancora migliore.


1
puoi anche definire gli operatori infix infix :) x |> f = f x
Jamie
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.