Converting F# pipeline operators ( <|, >>, << ) to OCaml

馋奶兔 提交于 2019-11-30 19:11:09

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)
bytebuster

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.

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)].

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.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!