Kompozycja Haskella (.) A operator potoku F # do przodu (|>)

100

W języku F # użycie operatora potoku do przodu |>jest dość powszechne. Jednak w Haskell widziałem tylko kompozycję funkcji (.), która jest używana. Rozumiem, że są one powiązane , ale czy jest jakiś powód językowy, dla którego funkcja potoku do przodu nie jest używana w Haskell, czy jest to coś innego?

Ben Lings
źródło
2
odpowiedź lihanseys stwierdza, że &należy do Haskella |>. Zagrzebany głęboko w tym wątku i odkrycie zajęło mi kilka dni. Używam go często, ponieważ naturalnie czytasz od lewej do prawej, aby postępować zgodnie z kodem.
itmuckel

Odpowiedzi:

61

Jestem trochę spekulatywny ...

Kultura : Myślę, że |>jest ważnym operatorem w „kulturze” języka F # i być może podobnie w .przypadku Haskella. F # ma operator kompozycji funkcji, <<ale myślę, że społeczność F # ma tendencję do mniejszego używania stylu bez punktów niż społeczność Haskell.

Różnice językowe : nie wiem wystarczająco dużo na temat obu języków, aby je porównać, ale być może reguły uogólniających powiązań let są na tyle różne, że mają na to wpływ. Na przykład wiem, że w F # czasami piszę

let f = exp

nie skompiluje się i potrzebujesz jawnej konwersji eta:

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

aby to skompilować. To także odwraca ludzi od stylu bez punktów / kompozycji i kieruje się w stronę stylu pipelining. Ponadto wnioskowanie o typie F # czasami wymaga potokowania, tak że znany typ pojawia się po lewej stronie (patrz tutaj ).

(Osobiście uważam, że styl bez punktów jest nieczytelny, ale przypuszczam, że każda nowa / inna rzecz wydaje się nieczytelna, dopóki się do tego nie przyzwyczaisz.)

Myślę, że oba są potencjalnie możliwe w każdym języku, a historia / kultura / przypadek mogą definiować, dlaczego każda społeczność osiedliła się w innym „atraktorze”.

Brian
źródło
7
Zgadzam się z różnicami kulturowymi. Tradycyjny Haskell wykorzystuje .i $, więc ludzie nadal ich używają.
Amok
9
Bez punktów jest czasem bardziej czytelny niż celowy, czasami mniej. Generalnie używam go w argumencie do funkcji takich jak map i filter, aby uniknąć zaśmiecania rzeczy przez lambdę. Czasami używam go również w funkcjach najwyższego poziomu, ale rzadziej i tylko wtedy, gdy jest to coś prostego.
Paul Johnson,
2
Nie widzę w tym zbytniej kultury, w tym sensie, że po prostu nie ma wielkiego wyboru w kwestii F # (z powodów, o których wspominacie ty i Ganesh). Powiedziałbym więc, że oba są opłacalne w Haskell, ale F # jest zdecydowanie lepiej wyposażony do korzystania z operatora potoku.
Kurt Schelfthout
2
tak, kultury czytania od lewej do prawej :) nawet matematyki tak powinno się uczyć ...
nicolas
1
@nicolas od lewej do prawej to wybór arbitralny. Możesz przyzwyczaić się do prawej do lewej.
Martin Capodici
86

W języku F # (|>)jest ważne ze względu na sprawdzanie pisma od lewej do prawej. Na przykład:

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

generalnie nie sprawdza typu, ponieważ nawet jeśli typ xsjest znany, typ argumentu xdo lambda nie jest znany w momencie, gdy sprawdzacz typu go widzi, więc nie wie, jak rozwiązać x.Value.

W przeciwieństwie

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

będzie działać dobrze, ponieważ rodzaj xsbędzie prowadził do typu xbycia znanym.

Sprawdzanie typu od lewej do prawej jest wymagane z powodu rozpoznawania nazw zaangażowanych w konstrukcje, takie jak x.Value. Simon Peyton Jones napisał propozycję dodania podobnego rodzaju rozpoznawania nazw do Haskella, ale sugeruje użycie lokalnych ograniczeń do śledzenia, czy typ obsługuje określoną operację, czy nie. Tak więc w pierwszej próbce wymaganie xwymagające Valuewłaściwości zostanie przeniesione do momentu, gdy xszostanie zauważone i można go rozwiązać. To jednak komplikuje system czcionek.

GS - Przeproś Monikę
źródło
1
Interesujące jest to, że istnieje operator (<|) podobny do (.) W Haskellu z tym samym kierunkiem danych od prawej do lewej. Ale jak będzie działała rozdzielczość typu?
The_Ghost
7
(<|) jest w rzeczywistości podobny do Haskella ($). Sprawdzanie czcionek od lewej do prawej jest wymagane tylko do rozwiązywania rzeczy takich jak .Value, więc (<|) działa dobrze w innych scenariuszach lub jeśli używasz jawnych adnotacji typu.
GS - Przeproś Monikę
44

Więcej spekulacji, tym razem ze strony z przewagą Haskella ...

($)jest odwrotnością (|>), a jej użycie jest dość powszechne, gdy nie można pisać kodu bez punktów. Więc głównym powodem, który (|>)nie jest używany w Haskell, jest to, że jego miejsce jest już zajęte ($).

Ponadto, mówiąc z doświadczenia F #, myślę, że (|>)jest tak popularny w kodzie F #, ponieważ przypomina Subject.Verb(Object)strukturę OO. Ponieważ F # ma na celu płynną integrację funkcjonalną / OO, Subject |> Verb Objectjest to całkiem płynne przejście dla nowych programistów funkcjonalnych.

Osobiście lubię też myśleć od lewej do prawej, więc używam (|>)w Haskell, ale nie sądzę, żeby wielu innych ludzi to robiło.

Nathan Shively-Sanders
źródło
Cześć Nathan, czy „flip ($)” jest predefiniowane w dowolnym miejscu platformy Haskell? Nazwa „(|>)” jest już zdefiniowana w Data.Sequence z innym znaczeniem. Jeśli jeszcze nie zdefiniowano, jak to nazywasz? Myślę o
wybraniu opcji
2
@mattbh: Nie to, co mogę znaleźć w Hoogle. Nie wiedziałem o tym Data.Sequence.|>, ale $>wydaje się rozsądne, aby uniknąć tam konfliktów. Szczerze mówiąc, jest tylko tak wielu dobrze wyglądających operatorów, więc użyłbym po prostu |>do obu i zarządzałem konfliktami na podstawie każdego przypadku. (Również kusiłbym, aby po prostu pseudonimować Data.Sequence.|>jako snoc)
Nathan Shively-Sanders,
2
($)i (|>)są aplikacją, a nie kompozycją. Oba są powiązane (jak notatki do pytań), ale nie są takie same (twoje fcdotyczy (Control.Arrow.>>>)funkcji).
Nathan Shively-Sanders,
11
W praktyce F # |>przypomina mi UNIX |bardziej niż cokolwiek innego.
Kevin Cantu
3
Inną zaletą języka |>F # jest to, że ma ładne właściwości dla IntelliSense w programie Visual Studio. Wpisz |>, a otrzymasz listę funkcji, które można zastosować do wartości po lewej stronie, podobnie jak w przypadku wpisywania .po obiekcie.
Kristopher Johnson
32

Myślę, że mylimy rzeczy. Haskell ( .) jest odpowiednikiem F # ( >>). Nie mylić z F # ( |>), która jest po prostu odwróconą aplikacją funkcji i jest jak Haskell ( $) - odwrócona:

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

Uważam, że programiści Haskell $często używają . Być może nie tak często, jak zwykle używają programiści F # |>. Z drugiej strony, niektórzy faceci z F # używają >>w śmiesznym stopniu: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx

AshleyF
źródło
2
jak mówisz, jest jak $operator Haskella - odwrócony, możesz go również łatwo zdefiniować jako: a |> b = flip ($)który staje się odpowiednikiem potoku F #, np. możesz to zrobić[1..10] |> map f
stosunku do
8
Myślę ( .) to samo co ( <<), podczas gdy ( >>) to kompozycja odwrotna. To znaczy ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3vs( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
Dobes Vandermeer
-1 dla „użyj >> w absurdalnym stopniu”. (Cóż, nie wiedziałem, ale zrozumiałeś). F w F # oznacza „funkcjonalny”, więc skład funkcji jest uzasadniony.
Florian F
1
Z pewnością zgadzam się, że kompozycja funkcji jest legalna. Przez „kilku facetów z F #” mam na myśli siebie! To mój własny blog. : P
AshleyF
Myślę, że nie .jest to równoważne >>. Nie wiem, czy F # ma, <<ale byłby to odpowiednik (jak w Elm).
Matt Joiner,
28

Jeśli chcesz używać F # |>w Haskell, to w Data.Function jest &operator (od base 4.8.0.0).

jhegedus
źródło
Każdy powód do Haskell do wyboru &ponad |>? Wydaje mi się, że |>jest znacznie bardziej intuicyjny, a także przypomina mi operatora potoku Unix.
yeshengm
@yeshengm uważam za &bardzo intuicyjne. Kod prawie czyta się poprawnie w języku angielskim, po prostu wymawiając &jako „i”. Na przykład: 5 & factorial & showczyta się na głos jako „weź 5, a następnie weź silnię z tego, a następnie zastosuj pokaz”.
Marcel Besixdouze
16

Kompozycja od lewej do prawej w Haskell

Niektórzy ludzie używają w Haskellu stylu od lewej do prawej (przekazywanie wiadomości). Zobacz na przykład bibliotekę mps w witrynie Hackage. Przykład:

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

Myślę, że ten styl wygląda ładnie w niektórych sytuacjach, ale jest trudniejszy do odczytania (trzeba znać bibliotekę i wszystkie jej operatory, redefinicja (.)też przeszkadza).

Istnieją również operatory kompozycji od lewej do prawej oraz od prawej do lewej w Control.Category , części pakietu podstawowego. Porównaj >>>i <<<odpowiednio:

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

Jest dobry powód, aby czasami preferować kompozycję od lewej do prawej: kolejność oceny jest zgodna z porządkiem czytania.

śastanin
źródło
Fajnie, więc (>>>) można w dużej mierze porównać z (|>)?
Khanzor
@Khanzor Niezupełnie. (|>) stosuje argument, (>>>) to głównie kompozycja funkcji (lub podobne rzeczy). Wtedy przypuszczam, że jest jakaś różnica w ustaleniach (nie sprawdzałem tego).
sastanin
15

Widziałem, jak >>>byłem używany flip (.)i często tego używam, szczególnie w przypadku długich łańcuchów, które najlepiej rozumieć od lewej do prawej.

>>> pochodzi z Control.Arrow i działa nie tylko na funkcjach.

spookylukey
źródło
>>>jest zdefiniowany w Control.Category.
Steven Shaw
13

Oprócz stylu i kultury sprowadza się to do optymalizacji projektu języka pod kątem czystego lub nieczystego kodu.

|>Operator jest powszechne w F # w dużej mierze dlatego, że pomaga ukryć dwa ograniczenia, które pojawiają się głównie z kodem-zanieczyszczonego:

  • Wnioskowanie o typie od lewej do prawej bez podtypów strukturalnych.
  • Ograniczenie wartości.

Należy zauważyć, że poprzednie ograniczenie nie istnieje w OCaml, ponieważ podtypy są strukturalne, a nie nominalne, więc typ strukturalny można łatwo doprecyzować poprzez ujednolicenie w miarę postępu wnioskowania o typie.

Haskell wybiera inny kompromis, wybierając skupienie się na czystym kodzie, w którym te ograniczenia można znieść.

JD
źródło
8

Myślę, że operator potoku F # do przodu ( |>) powinien vs ( & ) w 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

Jeśli nie lubisz &operatora ( ), możesz go dostosować, na przykład F # lub Elixir:

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

Dlaczego infixl 1 |>? Zobacz dokument w Data-Function (&)

infixl = wrostek + lewe skojarzenie

infixr = wrostek + prawe skojarzenie


(.)

( .) oznacza kompozycję funkcji. Oznacza to (fg) (x) = f (g (x)) w Math.

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

to jest równe

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

lub

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

( $) operator jest również zdefiniowany w Data-Function ($) .

( .) służy do tworzenia Hight Order Functionlub closure in js. Zobacz przykład:


// (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, mniej (kod) jest lepsze.


Porównaj |>i.

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

To jest różnica między left —> righti right —> left. ⊙﹏⊙ |||

Uczłowieczenie

|>i &jest lepszy niż.

ponieważ

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

Jak programować funkcjonalnie w języku obiektowym?

odwiedź http://reactivex.io/

Obsługuje:

  • Java: RxJava
  • JavaScript: RxJS
  • C #: Rx.NET
  • C # (jedność): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C ++: RxCpp
  • Lua: RxLua
  • Ruby: Rx.rb
  • Python: RxPY
  • Idź: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Kotlin: RxKotlin
  • Swift: RxSwift
  • PHP: RxPHP
  • Elixir: reaxive
  • Dart: RxDart
lihansey
źródło
1

To mój pierwszy dzień, aby wypróbować Haskell (po Rust i F #) i udało mi się zdefiniować operator F # |>:

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

i wydaje się, że działa:

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

main =     
    5 |> factorial |> print

Założę się, że ekspert Haskell może dać ci jeszcze lepsze rozwiązanie.

tib
źródło
1
możesz także zdefiniować operatory wrostkowe infix :) x |> f = f x
Jamie