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?
Roughly, application ( $ ) is used between a function and its argument (a list in this case), while composition ( . ) is used between two functions. About pattern matching: in the first solution, the function reverse will perform the needed pattern match for you, so myLast does not have to.
Nevertheless function composition in Haskell is right associative: infixr 9 .
A higher-order function is a function that takes other functions as arguments or returns a function as result.
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.
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With