Haskell composition (.) vs F#'s pipe forward operator (|>)
Asked Answered
S

10

110

In F#, use of the the pipe-forward operator, |>, is pretty common. However, in Haskell I've only ever seen function composition, (.), being used. I understand that they are related, but is there a language reason that pipe-forward isn't used in Haskell or is it something else?

Steelman answered 21/9, 2009 at 22:4 Comment(1)
lihanseys answer states that & is Haskell's |>. Buried deep in this thread and took me a few days to discover. I use it a lot, because you naturally read left to right to follow your code.Dib
B
64

I am being a little speculative...

Culture: I think |> is an important operator in the F# "culture", and perhaps similarly with . for Haskell. F# has a function composition operator << but I think the F# community tends to use points-free style less than the Haskell community.

Language differences: I don't know enough about both languages to compare, but perhaps the rules for generalizing let-bindings are sufficiently different as to affect this. For example, I know in F# sometimes writing

let f = exp

will not compile, and you need explicit eta-conversion:

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

to make it compile. This also steers people away from points-free/compositional style, and towards the pipelining style. Also, F# type inference sometimes demands pipelining, so that a known type appears on the left (see here).

(Personally, I find points-free style unreadable, but I suppose every new/different thing seems unreadable until you become accustomed to it.)

I think both are potentially viable in either language, and history/culture/accident may define why each community settled at a different "attractor".

Bonnice answered 21/9, 2009 at 22:26 Comment(7)
I agree with the cultural differences. Traditional Haskell makes use of . and $, so people continue to use them.Charles
Point free is sometimes more readable than pointful, sometimes less. I generally use it in the argument to functions like map and filter, to avoid having a lambda cluttering things up. I sometimes use it in top-level functions too, but less often and only when its something straightforward.Airplane
I don't see much culture in it, in the sense that there simply isn't much of a choice in the matter as far as F# is concerned (for the reasons you and Ganesh mention). So I'd say that both are viable in Haskell, but F# is definitely much better equipped for using the pipeline operator.Bustos
yes, the culture of reading left to right :) even math should be taught that way...Gil
@Gil left to right is an arbitrary choice. You can get used to right to left.Ailis
@MartinCapodici indeed. some people go top to bottom then right to left. it's all relative and arbitrary, and we can adapt to everything, but that's a painGil
F# also has a composition operator >> that is essentially composition in left-to-right reading order. I.e., let h = f >> g means let h x = x |> f |> g. This allows you to write function composition that looks like the |> pipeline, but in point-free style: let chain = add2 >> mult3 >> add1 is equivalent to let chain x = x |> add2 |> mult3 |> add1, and reads in the same order.Sciolism
F
92

In F# (|>) is important because of the left-to-right typechecking. For example:

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

generally won't typecheck, because even if the type of xs is known, the type of the argument x to the lambda isn't known at the time the typechecker sees it, so it doesn't know how to resolve x.Value.

In contrast

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

will work fine, because the type of xs will lead to the type of x being known.

The left-to-right typechecking is required because of the name resolution involved in constructs like x.Value. Simon Peyton Jones has written a proposal for adding a similar kind of name resolution to Haskell, but he suggests using local constraints to track whether a type supports a particular operation or not, instead. So in the first sample the requirement that x needs a Value property would be carried forward until xs was seen and this requirement could be resolved. This does complicate the type system, though.

Frobisher answered 22/9, 2009 at 10:12 Comment(2)
Interesting this is that there is (<|) operator similar to (.) in Haskell with the same direction of data right-to-left. But how will work type resolution for it?Statement
(<|) is actually similar to Haskell's ($). Left-to-right typechecking is only required for resolving things like .Value, so (<|) works fine in other scenarios, or if you use explicit type annotations.Frobisher
B
64

I am being a little speculative...

Culture: I think |> is an important operator in the F# "culture", and perhaps similarly with . for Haskell. F# has a function composition operator << but I think the F# community tends to use points-free style less than the Haskell community.

Language differences: I don't know enough about both languages to compare, but perhaps the rules for generalizing let-bindings are sufficiently different as to affect this. For example, I know in F# sometimes writing

let f = exp

will not compile, and you need explicit eta-conversion:

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

to make it compile. This also steers people away from points-free/compositional style, and towards the pipelining style. Also, F# type inference sometimes demands pipelining, so that a known type appears on the left (see here).

(Personally, I find points-free style unreadable, but I suppose every new/different thing seems unreadable until you become accustomed to it.)

I think both are potentially viable in either language, and history/culture/accident may define why each community settled at a different "attractor".

Bonnice answered 21/9, 2009 at 22:26 Comment(7)
I agree with the cultural differences. Traditional Haskell makes use of . and $, so people continue to use them.Charles
Point free is sometimes more readable than pointful, sometimes less. I generally use it in the argument to functions like map and filter, to avoid having a lambda cluttering things up. I sometimes use it in top-level functions too, but less often and only when its something straightforward.Airplane
I don't see much culture in it, in the sense that there simply isn't much of a choice in the matter as far as F# is concerned (for the reasons you and Ganesh mention). So I'd say that both are viable in Haskell, but F# is definitely much better equipped for using the pipeline operator.Bustos
yes, the culture of reading left to right :) even math should be taught that way...Gil
@Gil left to right is an arbitrary choice. You can get used to right to left.Ailis
@MartinCapodici indeed. some people go top to bottom then right to left. it's all relative and arbitrary, and we can adapt to everything, but that's a painGil
F# also has a composition operator >> that is essentially composition in left-to-right reading order. I.e., let h = f >> g means let h x = x |> f |> g. This allows you to write function composition that looks like the |> pipeline, but in point-free style: let chain = add2 >> mult3 >> add1 is equivalent to let chain x = x |> add2 |> mult3 |> add1, and reads in the same order.Sciolism
L
47

More speculation, this time from the predominantly Haskell side...

($) is the flip of (|>), and its use is quite common when you can't write point-free code. So the main reason that (|>) not used in Haskell is that its place is already taken by ($).

Also, speaking from a bit of F# experience, I think (|>) is so popular in F# code because it resembles the Subject.Verb(Object) structure of OO. Since F# is aiming for a smooth functional/OO integration, Subject |> Verb Object is a pretty smooth transition for new functional programmers.

Personally, I like thinking left-to-right too, so I use (|>) in Haskell, but I don't think many other people do.

Libertinage answered 22/9, 2009 at 6:57 Comment(6)
Hi Nathan, is "flip ($)" predefined anywhere in the Haskell platform? The name "(|>)" is already defined in Data.Sequence with another meaning. If not already defined, what do you call it? I'm thinking of going with "($>) = flip ($)"Ikon
@mattbh: Not that I can find with Hoogle. I didn't know about Data.Sequence.|>, but $> looks reasonable to avoid conflicts there. Honestly, there are only so many good-looking operators, so I would just use |> for both and manage conflicts on a case-by-case basis. (Also I would be tempted to just alias Data.Sequence.|> as snoc)Libertinage
($) just redefines the parsing associativity. It's not the same as forward composition -- you have to have names bound in scope. Try it yourself: pastebin.com/uEc2k612Flinders
($) and (|>) are application not composition. The two are related (as the question notes) but they are not the same (your fc is (Control.Arrow.>>>) for functions).Libertinage
In practice, F#'s |> actually reminds me of the UNIX | more than anything else.Presumptive
Another benefit of |> in F# is that it has nice properties for IntelliSense in Visual Studio. Type |>, and you get a list of functions that can be applied to the value on the left, similar to what happens when typing . after an object.Outshine
H
34

I think we're confusing things. Haskell's (.) is equivalent to F#'s (>>). Not to be confused with F#'s (|>) which is just inverted function application and is like Haskell's ($) - reversed:

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

I believe Haskell programmers do use $ often. Perhaps not as often as F# programmers tend to use |>. On the other hand, some F# guys use >> to a ridiculous degree: http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx

Haematoma answered 22/4, 2011 at 17:48 Comment(5)
as you say it's like Haskell's $ operator - reversed, you can also easily defined it as: a |> b = flip ($) which becomes equivalent to F#'s pipeline e.g. you can then do [1..10] |> map fCoulter
I think (.) the same as (<<), whereas (>>) is the reverse composition. That is ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3 vs ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3Isochor
-1 for "use >> to a ridiculous degree". (Well, I didn't but you got my point). F in F# is for "functional", so function composition is legitimate.Haematin
Certainly I agree that function composition is legit. By "some F# guys" I'm referring to myself! That my own blog. :PHaematoma
I don't think . is equivalent to >>. I don't know if F# has << but that would be the equivalent (like in Elm).Bestride
I
32

If you want to use F#'s |> in Haskell then in Data.Function is the & operator (since base 4.8.0.0).

Infusorian answered 27/9, 2015 at 8:31 Comment(2)
Any reason for Haskell to choose & over |>? I feel like |> is much more intuitive, and it also reminds me of Unix pipe operator.Octodecimo
@Octodecimo I find & very intuitive. Code nearly reads correctly in English by just pronouncing & as "and". For example: 5 & factorial & show reads aloud as "take 5 and then take factorial of it and then apply show to it".Africanize
B
17

I have seen >>> being used for flip (.), and I often use that myself, especially for long chains that are best understood left-to-right.

>>> is actually from Control.Arrow, and works on more than just functions.

Brote answered 2/10, 2009 at 0:35 Comment(1)
>>> is defined in Control.Category.Kirwin
B
17

Left-to-right composition in Haskell

Some people use left-to-right (message-passing) style in Haskell too. See, for example, mps library on Hackage. An example:

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

I think this style looks nice in some situations, but it's harder to read (one needs to know the library and all its operators, the redefined (.) is disturbing too).

There are also left-to-right as well as right-to-left composition operators in Control.Category, part of the base package. Compare >>> and <<< respectively:

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

There is a good reason to prefer left-to-right composition sometimes: evaluation order follows reading order.

Bolometer answered 26/5, 2010 at 14:41 Comment(2)
Nice, so (>>>) is largely equatable to (|>)?Plug
@Plug Not exactly. (|>) applies an argument, (>>>) is mostly function composition (or similar things). Then I suppose there is some fixity difference (didn't check it).Bolometer
B
17

I think F#'s pipe forward operator (|>) should 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

If you dont like (&) operator, you can custom it like F# or Elixir :

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

Why infixl 1 |>? See the doc in Data-Function (&)

infixl = infix + left associativity

infixr = infix + right associativity


(.)

(.) means function composition. It means (f.g)(x) = f(g(x)) in Math.

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

it equals

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

or

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

($) operator is also defind in Data-Function ($).

(.) is used for create Hight Order Function or closure in js. See example:


// (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) is better.


Compare |> and .

ghci> 5 |> factorial |> show

// equals

ghci> (show . factorial) 5 

// equals

ghci> show . factorial $ 5 

It is the different between left —> right and right —> left. ⊙﹏⊙|||

Humanization

|> and & is better than .

because

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

How to functional programming in object-oriented language?

please visit http://reactivex.io/

It support :

  • Java: RxJava
  • JavaScript: RxJS
  • C#: Rx.NET
  • C#(Unity): UniRx
  • Scala: RxScala
  • Clojure: RxClojure
  • C++: RxCpp
  • Lua: RxLua
  • Ruby: Rx.rb
  • Python: RxPY
  • Go: RxGo
  • Groovy: RxGroovy
  • JRuby: RxJRuby
  • Kotlin: RxKotlin
  • Swift: RxSwift
  • PHP: RxPHP
  • Elixir: reaxive
  • Dart: RxDart
Becoming answered 15/5, 2019 at 8:34 Comment(1)
The only issue that I see with replacing |> in FSharp with & in Haskell is that you can (allegedly) not use it at the start of a line.Stertorous
F
15

Aside from style and culture, this boils down to optimizing the language design for either pure or impure code.

The |> operator is common in F# largely because it helps to hide two limitations that appear with predominantly-impure code:

  • Left-to-right type inference without structural subtypes.
  • The value restriction.

Note that the former limitation does not exist in OCaml because subtyping is structural instead of nominal, so the structural type is easily refined via unification as type inference progresses.

Haskell takes a different trade-off, choosing to focus on predominantly-pure code where these limitations can be lifted.

Fango answered 23/4, 2011 at 23:47 Comment(0)
I
1

This is my first day to try Haskell (after Rust and F#), and I was able to define F#'s |> operator:

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

and it seems to work:

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

main =     
    5 |> factorial |> print

I bet a Haskell expert can give you an even better solution.

Ileum answered 10/8, 2017 at 20:5 Comment(1)
you can also define infix operators infix :) x |> f = f xWatercourse

© 2022 - 2024 — McMap. All rights reserved.