问题
I'm converting some F# code to OCaml and I see a lot of uses of this pipeline operator <|, for example:
let printPeg expr =
printfn "%s" <| pegToString expr
The <| operator is apparently defined as just:
# let ( <| ) a b = a b ;;
val ( <| ) : ('a -> 'b) -> 'a -> 'b = <fun>
I'm wondering why they bother to define and use this operator in F#, is it just so they can avoid putting in parens like this?:
let printPeg expr =
Printf.printf "%s" ( pegToString expr )
As far as I can tell, that would be the conversion of the F# code above to OCaml, correct?
Also, how would I implement F#'s << and >> operators in Ocaml?
( the |> operator seems to simply be: let ( |> ) a b = b a ;; )
回答1:
Directly from the F# source:
let inline (|>) x f = f x
let inline (||>) (x1,x2) f = f x1 x2
let inline (|||>) (x1,x2,x3) f = f x1 x2 x3
let inline (<|) f x = f x
let inline (<||) f (x1,x2) = f x1 x2
let inline (<|||) f (x1,x2,x3) = f x1 x2 x3
let inline (>>) f g x = g(f x)
let inline (<<) f g x = f(g x)
回答2:
why they bother to define and use this operator in F#, is it just so they can avoid putting in parens?
It's because the functional way of programming assumes threading a value through a chain of functions. Compare:
let f1 str server =
str
|> parseUserName
|> getUserByName server
|> validateLogin <| DateTime.Now
let f2 str server =
validateLogin(getUserByName(server, (parseUserName str)), DateTime.Now)
In the first snippet, we clearly see everything that happens with the value. Reading the second one, we have to go through all parens to figure out what's going on.
This article about function composition seems to be relevant.
So yes, in a regular life, it is mostly about parens. But also, pipeline operators are closely related to partial function application and point-free style of coding. See Programming is "Pointless", for example.
The pipeline |> and function composition >> << operators can produce yet another interesting effect when they are passed to higher-level functions, like here.
回答3:
OCaml Batteries supports these operators, but for reasons of precedence, associativity and other syntactic quirks (like Camlp4) it uses different symbols. Which particular symbols to use has just been settled recently, there are some changes. See: Batteries API:
val (|>) : 'a -> ('a -> 'b) -> 'b
Function application. x |> f is equivalent to f x.
val ( **> ) : ('a -> 'b) -> 'a -> 'b
Function application. f **> x is equivalent to f x. Note The name of this operator is not written in stone. It is bound to change soon.
val (|-) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
Function composition. f |- g is fun x -> g (f x). This is also equivalent to applying <** twice.
val (-|) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b
Function composition. f -| g is fun x -> f (g x). Mathematically, this is operator o.
But Batteries trunk provides:
val ( @@ ) : ('a -> 'b) -> 'a -> 'b
Function application. [f @@ x] is equivalent to [f x].
val ( % ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b
Function composition: the mathematical [o] operator.
val ( |> ) : 'a -> ('a -> 'b) -> 'b
The "pipe": function application. [x |> f] is equivalent to [f x].
val ( %> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
Piping function composition. [f %> g] is [fun x -> g (f x)].
回答4:
I'm wondering why they bother to define and use this operator in F#, is it just so they can avoid putting in parens like this?
Excellent question. The specific operator you're referring to (<|) is pretty useless IME. It lets you avoid parentheses on rare occasions but more generally it complicates the syntax by dragging in more operators and makes it harder for less experienced F# programmers (of which there are now many) to understand your code. So I've stopped using it.
The |> operator is much more useful but only because it helps F# to infer types correctly in situations where OCaml would not have a problem. For example, here is some OCaml:
List.map (fun o -> o#foo) os
The direct equivalent fails in F# because the type of o cannot be inferred prior to reading its foo property so the idiomatic solution is to rewrite the code like this using the |> so F# can infer the type of o before foo is used:
os |> List.map (fun o -> o.foo)
I rarely use the other operators (<< and >>) because they also complicate the syntax. I also dislike parser combinator libraries that pull in lots of operators.
The example Bytebuster gave is interesting:
let f1 str server =
str
|> parseUserName
|> getUserByName server
|> validateLogin <| DateTime.Now
I would write this as:
let f2 str server =
let userName = parseUserName str
let user = getUserByName server userName
validateLogin user DateTime.Now
There are no brackets in my code. My temporaries have names so they appear in the debugger and I can inspect them and Intellisense can give me type throwback when I hover the mouse over them. These characteristics are valuable for production code that non-expert F# programmers will be maintaining.
来源:https://stackoverflow.com/questions/14410953/converting-f-pipeline-operators-to-ocaml