The work around is changing code from
external ff : int -> int = "" [@@bs.send.pipe : t]
to
external ff : t -> int -> int = "" [@@bs.send]
let ff a b = ff b a
With such change, it will not affect end users (slightly slower), eventually we want users just use bs.send with the new pipe syntax
external ff : t -> int -> int = "" [@@bs.send]
let () = a |. ff 2
The motivation is to reduce the complexity of FFI and maintainance overhead, but also encourage people to embrace t first convention
reduce the complexity of FFI
That'd be fantastic
When should we use the OCaml |> and when should we use the pipe syntax |. (or ->) ?
For API providers: which one should I encourage the consumers to use?
For API consumers: which one should I use, when both are applicable?
I would recommend avoiding the fast pipe entirely, since it mostly just causes confusion. But especially if you're writing anything not exclusively targeting BuckleScript, since the fast pipe is a BuckleScript-exclusive weirdness.
Using the fast pipe prevents you from designing functions in a way that encourages function composition through partial application. For example, if you want to extract all the values from a list of options while providing a default value for those that are None, you can use List.map over Option.getWithDefault. With functions designed for the fast pipe, by putting the "target" argument first, you have to write:
/* Reason */
items |. List.map(item => item |. Option.getWithDefault("default"));
(* OCaml *)
items |. List.map (fun item -> item |. Option.getWithDefault "default");
md5-1741038b3c90aa9863ef2658067e5f9d
```reason
(* OCaml *)
items |> List.map (Option.getWithDefault "default");
because Option.getWithDefault("default") is partially applied and will due to currying return a function that matches what List.map expects.
This example is pretty trivial, but the gains quickly add up. There are very good reasons why functional languages have mostly standardized on |>, and why even those that don't have a standardized pipe operator (like Haskell) still use "t last" and are curried in the first place.
Thanks! Now |> makes good sense for me if I'm working with those standard libraries.
But what if I'm writing JS FFI bindings (hence exclusively BuckleScript), should I put the t in the front or as the last argument?
It seems like there are people advocating and converting their code with this "object-first convention" to make use of the BuckleScript fast pipe.
I would still recommend not using fast pipe. There is a performance benefit to using |. over |>, but there's no performance benefit over not using any pipe operator, when performance is actually needed. The problem with the fast pipe is that it forces a performance by default design that is very rarely beneficial, just for the sake of premature optimization.
Take it from Donald Knuth:
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.
In web front-end programming especially, the time spent in code you write is usually dwarfed by the time spent updating the DOM.
external ff : t -> int -> int = "" [@@bs.send]
let ff a b = ff b a
I don't really mind the performance cost.
However, it's the need of the extra line that makes it painful, and isn't that what [@@bs.send.pipe : t] was meant for?
@bobzhang what should we do about this?
We plan to make the external FFI handling logic simplified in the core, but it is not done since it may break some existing code. bs.send.pipe is the single attribute which will tweak the type signature, so it also complicates a bit for the user.
There are lots of benefit using fast pipe, but people may disagree, so I would not trigger a flaming war here.
I would love to see those lots of benefits clearly explained sometime. The only rationale we've gotten so far, as far as I'm aware, is this comment and the thread that follows, which 1. doesn't really explain much and 2. ignores any criticism and concern raised.
You can't expect anyone to "agree" with you when you go about it like that, and I don't think it's going to help your cause to characterize those who therefore don't understand, or don't agree with whatever assumptions you're making, as potential flamers.
@glennsl I think we already talked about it offline for more than several hours.
Let me summarize it below (other people may not know details about it)
'a -> 'b -> ('c -> 'd) for example, in t-last convention, it is hard to tell if 'c is t or 'b.compare, startsWithbs.send alone|> is less efficient than |. in both compile time and runtime|> introduced more type annotation than |. due to the ocaml type inference algorithm|> introduced slower code compared with |. (esp true when we upgrade to OCaml 4.06) due to OCaml type information flow direction|> makes currying easier is arguable, it is not my own view: accidental currying is harmful|> is widely used is arguable, janestreet adopted t first convention, even in OCaml stdlib, it is not consistent, take compare for exampleI already spent lots of time explaining this issue, let's agree to disagree.
|> will always be preservedA summary of what? This is just a reiteration of your original points, ignoring any criticism and concern that has been raised. You are intentionally misrepresenting the issue.
Here's a summary of the other side:
|> _is_ less efficient than |.. This is the only argument that seems to hold up. However, it's not less efficient than not using any pipe operator at all when that extra bit of performance is actually needed. |> could also be made just as efficient as |. by treating it as a syntactic transform, at the small cost of changing order of evaluation, which is more of a theoretical than a practical problem.|. could prevent anything like that.|> and |. causes a lot of confusion about which to use when, especially when you have to continuously switch between them. Having to remember what "kind" of function each and every function is in order to use the correct pipe operator on them is a heavy cognitive burden. This alone is a major issue that deserves proper justification, but you haven't even acknowledged that it _is_ an issue yet.What I'm asking for is a clear and thorough explanation that looks at all sides. Despite all the hours you say you have spent explaining this, you seem to be incapable of doing that. Why is that? Do you just not care?
Mentioning Jane Street's practice of "t first" without mentioning they only use a single positional argument is indeed a bit misleading. They use the classic pipe operator everywhere.
I'd love to have a BeltLabels module, you can take a look at Containers for an example of a stdlib that automatically generates both conventions: https://github.com/c-cube/ocaml-containers/pull/233
|> could also be made just as efficient as |. by treating it as a syntactic transform
Enough is enough.