Haskell-Komposition (.) Gegen F # 's Pipe-Forward-Operator (|>)

100

In F # ist die Verwendung des Pipe-Forward-Operators |>ziemlich häufig. In Haskell habe ich jedoch nur die Funktionszusammensetzung gesehen (.), die verwendet wurde. Ich verstehe, dass sie verwandt sind , aber gibt es einen sprachlichen Grund, warum Pipe-Forward in Haskell nicht verwendet wird, oder ist es etwas anderes?

Ben Lings
quelle
2
Lihanseys Antwort besagt, dass dies &Haskells ist |>. Tief in diesem Thread vergraben und ich brauchte ein paar Tage, um es zu entdecken. Ich benutze es oft, weil Sie natürlich von links nach rechts lesen, um Ihrem Code zu folgen.
Itmuckel

Antworten:

61

Ich bin ein wenig spekulativ ...

Kultur : Ich denke, |>ist ein wichtiger Operator in der F # "Kultur", und vielleicht ähnlich mit .für Haskell. F # hat einen Funktionskompositionsoperator, <<aber ich denke, die F # -Community verwendet tendenziell weniger punktefreien Stil als die Haskell-Community.

Sprachunterschiede : Ich weiß nicht genug über beide Sprachen, um sie zu vergleichen, aber vielleicht sind die Regeln für die Verallgemeinerung von Let-Bindungen ausreichend unterschiedlich, um dies zu beeinflussen. Zum Beispiel weiß ich in F # manchmal schreiben

let f = exp

wird nicht kompiliert und Sie benötigen eine explizite eta-Konvertierung:

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

um es kompilieren zu lassen. Dies lenkt die Menschen auch vom punktfreien / kompositorischen Stil weg und zum Pipelining-Stil. Außerdem erfordert die Inferenz vom Typ F # manchmal ein Pipelining, sodass links ein bekannter Typ angezeigt wird (siehe hier ).

(Ich persönlich finde den punktefreien Stil unlesbar, aber ich nehme an, dass jedes neue / andere Ding unlesbar erscheint, bis Sie sich daran gewöhnt haben.)

Ich denke, beide sind in beiden Sprachen potenziell lebensfähig, und Geschichte / Kultur / Unfall können definieren, warum sich jede Gemeinde bei einem anderen "Attraktor" niedergelassen hat.

Brian
quelle
7
Ich stimme den kulturellen Unterschieden zu. Das traditionelle Haskell nutzt .und $, so dass die Leute sie weiterhin benutzen.
Amok
9
Punktfrei ist manchmal besser lesbar als Punkt, manchmal weniger. Ich benutze es im Allgemeinen im Argument, um Funktionen wie Map und Filter zu verwenden, um zu vermeiden, dass ein Lambda die Dinge durcheinander bringt. Ich benutze es manchmal auch in Funktionen der obersten Ebene, aber seltener und nur dann, wenn es etwas Unkompliziertes ist.
Paul Johnson
2
Ich sehe nicht viel Kultur darin, in dem Sinne, dass es in Bezug auf F # einfach keine große Auswahl gibt (aus den Gründen, die Sie und Ganesh erwähnen). Ich würde also sagen, dass beide in Haskell lebensfähig sind, aber F # ist definitiv viel besser für die Verwendung des Pipeline-Betreibers gerüstet.
Kurt Schelfthout
2
Ja, die Kultur des Lesens von links nach rechts :) Sogar Mathematik sollte so gelehrt werden ...
Nicole
1
@nicolas von links nach rechts ist eine willkürliche Wahl. Man kann sich von rechts nach links gewöhnen.
Martin Capodici
86

In F # (|>)ist wegen der Links-Rechts-Typprüfung wichtig. Beispielsweise:

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

Im Allgemeinen wird der Typcheck nicht überprüft, da selbst wenn der Typ von xsbekannt ist, der Typ des Arguments xfür das Lambda zum Zeitpunkt des Typecheckers nicht bekannt ist und daher nicht weiß, wie es aufgelöst werden soll x.Value.

Im Gegensatz

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

wird gut funktionieren, weil die Art von xswird dazu führen, dass die Art xbekannt ist.

Die Typüberprüfung von links nach rechts ist aufgrund der Namensauflösung erforderlich, die bei Konstrukten wie z x.Value. Simon Peyton Jones hat einen Vorschlag geschrieben , Haskell eine ähnliche Art der Namensauflösung hinzuzufügen. Er schlägt jedoch vor, lokale Einschränkungen zu verwenden, um zu verfolgen, ob ein Typ stattdessen eine bestimmte Operation unterstützt oder nicht. In der ersten Stichprobe würde die Anforderung, xdie eine ValueEigenschaft benötigt, so lange vorgetragen, bis xssie erkannt wurde und diese Anforderung gelöst werden könnte. Dies erschwert jedoch das Typsystem.

GS - Entschuldige dich bei Monica
quelle
1
Interessant ist, dass es in Haskell einen (<|) ähnlichen Operator wie (.) Mit der gleichen Datenrichtung von rechts nach links gibt. Aber wie wird die Typauflösung dafür funktionieren?
The_Ghost
7
(<|) ähnelt tatsächlich Haskells ($). Die Typüberprüfung von links nach rechts ist nur zum Auflösen von Dingen wie .Value erforderlich. Daher funktioniert (<|) in anderen Szenarien oder wenn Sie explizite Typanmerkungen verwenden.
GS - Entschuldigen Sie sich bei Monica
44

Weitere Spekulationen, diesmal von der überwiegend Haskell-Seite ...

($)ist das Umdrehen von (|>), und seine Verwendung ist ziemlich häufig, wenn Sie keinen punktfreien Code schreiben können. Der Hauptgrund, (|>)der in Haskell nicht verwendet wird, ist, dass sein Platz bereits von eingenommen wird ($).

Aus ein wenig F # -Erfahrung heraus denke ich, dass (|>)es im F # -Code so beliebt ist, weil es der Subject.Verb(Object)Struktur von OO ähnelt . Da F # eine reibungslose Funktions- / OO-Integration anstrebt, Subject |> Verb Objectist dies ein ziemlich reibungsloser Übergang für neue Funktionsprogrammierer.

Persönlich denke ich auch gerne von links nach rechts, also benutze ich es (|>)in Haskell, aber ich glaube nicht, dass es viele andere Leute tun.

Nathan Shively-Sanders
quelle
Hallo Nathan, ist "flip ($)" irgendwo auf der Haskell-Plattform vordefiniert? Der Name "(|>)" ist bereits in Data.Sequence mit einer anderen Bedeutung definiert. Wenn nicht bereits definiert, wie nennt man das? Ich denke daran, mit "($>) = flip ($)"
mattbh
2
@mattbh: Nicht dass ich mit Hoogle finden kann. Ich wusste es nicht Data.Sequence.|>, aber es $>sieht vernünftig aus, um Konflikte dort zu vermeiden. Ehrlich gesagt gibt es nur so viele gut aussehende Operatoren, dass ich sie nur |>für beide verwenden und Konflikte von Fall zu Fall verwalten würde. (Auch ich wäre versucht, nur alias Data.Sequence.|>als snoc)
Nathan Shively-Sanders
2
($)und (|>)sind Anwendung nicht Zusammensetzung. Die beiden sind verwandt (wie die Frage vermerkt), aber sie sind nicht gleich (Ihr fcist (Control.Arrow.>>>)für Funktionen).
Nathan Shively-Sanders
11
In der Praxis |>erinnert mich F # |mehr als alles andere an UNIX .
Kevin Cantu
3
Ein weiterer Vorteil von |>F # ist, dass es nette Eigenschaften für IntelliSense in Visual Studio hat. Geben |>Sie ein, und Sie erhalten eine Liste der Funktionen, die auf den Wert auf der linken Seite angewendet werden können, ähnlich wie beim Tippen .nach einem Objekt.
Kristopher Johnson
32

Ich denke, wir verwirren die Dinge. Haskells ( .) entspricht F # ( >>). Nicht zu verwechseln mit F # 's ( |>), das nur eine invertierte Funktionsanwendung ist und Haskells ( $) ähnelt - umgekehrt:

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

Ich glaube, Haskell-Programmierer verwenden $oft. Vielleicht nicht so oft wie F # -Programmierer |>. Auf der anderen Seite verwenden einige F # -Typen >>in lächerlichem Maße: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx

AshleyF
quelle
2
Wie Sie sagen, ist es wie der $Operator von Haskell - umgekehrt, Sie können ihn auch leicht definieren als: a |> b = flip ($)Dies entspricht der Pipeline von F #, z. B. können Sie dies dann tun[1..10] |> map f
gegenüber dem
8
Ich denke ( .) das gleiche wie ( <<), während ( >>) die umgekehrte Zusammensetzung ist. Das ist ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3vs( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
Dobes Vandermeer
-1 für "in lächerlichem Maße >> verwenden". (Nun, ich habe es nicht getan, aber du hast meinen Standpunkt verstanden). F in F # steht für "funktional", daher ist die Funktionszusammensetzung legitim.
Florian F
1
Natürlich stimme ich zu, dass die Funktionszusammensetzung legitim ist. Mit "einigen F # Jungs" beziehe ich mich auf mich selbst! Das ist mein eigener Blog. : P
AshleyF
Ich denke nicht, dass .es gleichbedeutend ist mit >>. Ich weiß nicht, ob F # hat, <<aber das wäre das Äquivalent (wie in Elm).
Matt Joiner
28

Wenn Sie F # |>in Haskell verwenden möchten, ist in Data.Function der &Operator (seit base 4.8.0.0).

jhegedus
quelle
Gibt es Gründe für Haskell wählen &über |>? Ich denke, es |>ist viel intuitiver und es erinnert mich auch an den Unix-Pipe-Operator.
Yeshengm
16

Komposition von links nach rechts in Haskell

Einige Leute verwenden auch in Haskell den Stil von links nach rechts (Nachrichtenübermittlung). Siehe zum Beispiel die mps- Bibliothek zu Hackage. Ein Beispiel:

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

Ich denke, dieser Stil sieht in manchen Situationen gut aus, ist aber schwieriger zu lesen (man muss die Bibliothek und alle ihre Operatoren kennen, die Neudefinition (.)ist auch störend).

In Control.Category , einem Teil des Basispakets , gibt es auch Kompositionsoperatoren von links nach rechts sowie von rechts nach links . Vergleichen >>>und <<<jeweils:

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

Es gibt einen guten Grund, manchmal die Komposition von links nach rechts zu bevorzugen: Die Bewertungsreihenfolge folgt der Lesereihenfolge.

Sastanin
quelle
Schön, also ist (>>>) weitgehend gleichzusetzen mit (|>)?
Khanzor
@ Khanzor Nicht genau. (|>) wendet ein Argument an, (>>>) ist meistens Funktionszusammensetzung (oder ähnliche Dinge). Dann gibt es wohl einen Unterschied in der Fixität (habe es nicht überprüft).
Sastanin
15

Ich habe gesehen >>>, wie man es benutzt flip (.), und ich benutze es oft selbst, besonders für lange Ketten, die am besten von links nach rechts verstanden werden.

>>> ist eigentlich von Control.Arrow und funktioniert mit mehr als nur Funktionen.

Spookylukey
quelle
>>>ist definiert in Control.Category.
Steven Shaw
13

Abgesehen von Stil und Kultur läuft dies darauf hinaus, das Sprachdesign für reinen oder unreinen Code zu optimieren.

Der |>Operator ist in F # vor allem deshalb üblich, weil er dazu beiträgt, zwei Einschränkungen zu verbergen, die bei überwiegend unreinem Code auftreten:

  • Typinferenz von links nach rechts ohne strukturelle Subtypen.
  • Die Wertebeschränkung.

Beachten Sie, dass die erstere Einschränkung in OCaml nicht existiert, da die Untertypisierung strukturell statt nominal ist, sodass der Strukturtyp im Verlauf der Typinferenz leicht durch Vereinheitlichung verfeinert werden kann.

Haskell geht einen anderen Kompromiss ein und konzentriert sich auf überwiegend reinen Code, bei dem diese Einschränkungen aufgehoben werden können.

JD
quelle
8

Ich denke, F # 's Pipe Forward Operator ( |>) sollte vs ( & ) in haskell sein.

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

Wenn Sie den &Operator ( ) nicht mögen , können Sie ihn wie F # oder Elixir anpassen:

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

Warum infixl 1 |>? Siehe das Dokument in Datenfunktion (&)

infixl = infix + linke Assoziativität

infixr = infix + rechte Assoziativität


(.)

( .) bedeutet Funktionszusammensetzung. Es bedeutet (fg) (x) = f (g (x)) in Mathe.

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

es ist gleich

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

oder

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

( $) Operator ist auch in Data-Function ($) defind .

( .) wird zum Erstellen Hight Order Functionoder verwendet closure in js. Siehe Beispiel:


// (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, weniger (Code) ist besser.


Vergleiche |>und.

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

Es ist der Unterschied zwischen left —> rightund right —> left. ⊙﹏⊙ |||

Humanisierung

|>und &ist besser als.

weil

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

Wie funktioniert man in objektorientierter Sprache?

Bitte besuchen Sie http://reactivex.io/

IT-Unterstützung :

  • Java: RxJava
  • JavaScript: RxJS
  • C #: Rx.NET
  • C # (Einheit): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C ++: RxCpp
  • Lua: RxLua
  • Ruby: Rx.rb.
  • Python: RxPY
  • Los: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Kotlin: RxKotlin
  • Swift: RxSwift
  • PHP: RxPHP
  • Elixier: reaxiv
  • Dart: RxDart
Lihansey
quelle
1

Dies ist mein erster Tag, an dem ich Haskell ausprobiert habe (nach Rust und F #), und ich konnte den Operator |> von F # definieren:

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

und es scheint zu funktionieren:

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

main =     
    5 |> factorial |> print

Ich wette, ein Haskell-Experte kann Ihnen eine noch bessere Lösung bieten.

tib
quelle
1
Sie können auch Infix-Operatoren Infix definieren :) x |> f = f x
Jamie