Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
F# Pipeline Operator in JavaScript with Babel (codereform.com)
114 points by galacticdessert on Feb 4, 2020 | hide | past | favorite | 94 comments


Somewhat related, but I wish that more languages provided a reserved convenience variable for accessing the result of the previous function call:

https://stackoverflow.com/questions/3326826/language-history...

  Python and Ruby: _
  Shell: $?
  HyperTalk: it (my first exposure to the concept, even though it was more of a convention)
  Some Lisps: *, **, ***, ...
If we had that, then we could write:

  func1();
  func2(_);
  func3(_);
  ...
Which reminds me of PostScript, which lets you examine and manipulate the program's stack directly (the top of the stack is the previous result):

http://www.ugrad.math.ubc.ca/Flat/intro.html

http://www.ugrad.math.ubc.ca/Flat/stack.html

If I were implementing it, I would make the previous result and stack contents read-only so that it's conceptually the same as the pipeline operator except that it allows inspecting the intermediate value.


I really like k/q’s right to left parsing. something like:

f(g(h(x)))

Becomes

f g h x

Which I find very natural and better than

x h g f

Like in a stack language.


> f(g(h(x)))

Haskell has $ operator (simple application operator but with low precedence—think open bracket that closes implicitly at the end of expression). While it makes your example a bit more verbose:

> f $ g $ h x

it also allows one to pass multiple arguments in an intermediate function call, e.g.

> f $ g x $ h y

is the same in Haskell as

> f (g x (h y))

which in most languages would be

> f(g(x, h(y))


Haskell also allows point free style:

    (f . g . h) x


The Stackoverflow thing you cite is wrong:

Ruby doesn't _ that way (IRB, the bundled Ruby REPL, does)—Ruby actually does have special handling for _ as a variable, but that's to suppress duplicate definitions n warnings to allow it to be used as a don't-care argument.

I think Python is similar, where it is special-cased in the REPL, not a general language feature.


Gets confusing in a multithreaded context.

Or if you return pointers.


There's also ReasonML for those looking to scratch the itch sooner.

Also, don't be turned off by the sea of comments. Pick one, they are all great. but yes ML language enthusiasts can be a bit overenthusiastic, but I don't fault them for that. They are all anxious to spread the gospel of ML. The Reason community on discord is excellent, one of the best I have had the pleasure to be a part of.

I didn't go with straight OCaml with js_of_ocaml because I wanted a better representation of what I was doing in JS land. There is an excellent write-up about it here: https://www.javierchavarri.com/js_of_ocaml-and-bucklescript/


Or just about any sexp-based languages. Implementing a basic -> clojure threading macro is simple. Placeholder arguments are simple as well.


And Elm & Purescript.


Can't us ML language enthusiasts just all get along? We're all aware of how vastly superior ML languages are for domain modeling and language readability :).


As long as you don't use Elm


Them's fighting words. I think Elm and F# have the most readable syntax of the ML languages I've encountered thus far.


The person you're responding to has it out for Elm. I recognize their username from an Elm thread where they spent their afternoon hating on it with punchlines like "it's designed for stupid people" and "nobody can take Elm seriously."

https://news.ycombinator.com/item?id=17842400 ctrl-f "vmchale"


And F# directly with Fable


or WebSharper

...or Blazor


For the record, this doesn't exist in PureScript by default (the default is the symbol `#`, which I don't find as pleasing on the eye). You need to define it:

    import Data.Function as F

    infixl 1 F.applyFlipped as |>


Wow, Purescript lets you define new operators and specify their type? (I assume infixl 1 is infix-left, what is the 1 for though)

That is really cool. I wish Haskell & Purescript weren't so difficult to grok, trying them has been on my todo-list for ages but they're so different than any other language.


Sure. You are basically specifying operators as aliases for functions, so all these are equivalent:

    F.applyFlipped blah rhubarb
    blah `F.applyFlipped` rhubarb
    blah |> rhubarb
    (|>) blah rhubarb
The number (1) is the precedence, so if you have multiple operators in a statement it knows how to group them.


Or OCaml with js_of_ocaml.


Or OCaml with BuckleScript


Why do people credit the pipe operator to F#? OCaml has it too, and F# is based on OCaml. Did the pipe operator get added to F# first? (The OCaml docs say that the pipe operator first appeared in OCaml 4.01 [1], and I can't find out when it got added to F#.)

[1] https://caml.inria.fr/pub/docs/manual-ocaml/libref/Stdlib.ht...


It appeared first in F#. The 4.01 version is quite recent actually.

F# was based on 3.xx, or maybe, even, 2.xx OCaml.

https://docs.microsoft.com/en-us/archive/blogs/dsyme/archeol...


The history of how the pipe operator got into F# is documented here: https://blogs.msdn.microsoft.com/dsyme/2011/05/17/archeologi...


Sounds like the idea came up April/May 1994 for F# then. Not sure when the feature landed.

Was added to OCaml 2013-09-12[0]

[0] https://ocaml.org/releases/4.01.0.html


Attribution in most cases is given to where the person first learned of the concept, not per se, the historical accuracy of who came first with the concept.

After all, it should rightfully be attributed to the Isabelle/ML programming lanaguage[0][1]

[0]https://isabelle.in.tum.de/ [1]https://docs.microsoft.com/en-us/archive/blogs/dsyme/archeol...


I was thinking the same thing, although it would be useful to have a pipeline operator in Javascript. My first thought would be UNIX when I think of pipes, not F#.


Yup


It's times like these that make me realize how spoiled I am by ClojureScript...the -> and ->> macros basically give me a pipe operator, and have been in the language from its inception :)

I'm actually glad that this kind of composition is growing in popularity. Back when I did F# for a living, I loved that by using the pipe operator, you could get something more or less akin to a fluent interface, without any direct coupling between the two composed functions, and without any gross intermediate variables. I have nothing against "regular" point-free composition or anything like that, but I do think that these pipe operators are easier to digest for a lot of purposes.


Those macros are just that, macros. Implementing them in any sexp-based language is trivial.

I think people are stuck in the "C MACROS ARE BAD" thinking. It is a really powerful feature. CLOS, the world's most flexible oo system, was conceived as a bunch of macros. Racket's pattern matching is really just a macro (that could almost be called a compiler). Useful things that can be syntactically integrated into a language as an afterthought, yet still look and feel first class.


No argument here; I'm a pretty big supporter of Lisps as a whole; most of my personal projects are done with Clojure (or ClojureScript) in no small part due to the power afforded to me by macros.

In fairness to a lot of people, I think that most of the folks in the "MACROS ARE BAD!!!" crowd are under the impression that C macros are the only way people do them, and assume that even Lisp macros are just are also just glorified text-expansion. I would be against macros too if that were the case.


I suspect that most of "MACROS ARE BAD!" folks only know Python or Java, and have never used a macro preprocessor, just like the "COBOL IS BAD!!" people of the past who had zero experience with Cobol.

> I would be against macros too if that were the case.

I wouldn't; even text expansion macros are useful, and can be done better than what is in C.

Using C preprocessing as an example criticize only textual/token macro expansion is still a strawman.


I find JavaScript unusable without this operator. Fortunately Babel supports it well.

It makes a great alternative to fiddling with prototypes or wrapper objects when you want to extend something.

For example, this is flat-map implemented as a free function:

    const flatMap = f => {
      if (!f) {
        throw new TypeError('f must be a function');
      }

      return xs => ({
        [Symbol.iterator]: function * () {
          for (const x of xs) {
            yield * f(x);
          }
        }
      });
    };


    // Usage
    const xs = [ 1, 2, 3 ] |> flatMap(x => [ x, -x ]);


For people who want something similar that is also typescript friendly, I wrote a babel macro that entirely gets compiled away during build.

https://github.com/ts-delight/pipe.macro


That fetch / await example is horrendous. Please don’t.


Anyone else think the async/await example looks terrible? Why do the keywords have to be on their own line?


I had created a solution to scratch my itch while waiting for the pipe operator which even got a logo when it got more than a hundred stars, for anyone interested: https://github.com/egeozcan/ppipe (needs ts typings though)


This is a neat one. In my current project, I created a (non-lazy) pipe function that take a value and a bunch of functions, composes the functions, and applies the value.

pipe(2, add(2), square, n => n + 7) // -> 23

Nothing fancy but comes in handy at times.

What I really miss is something like the |> from Elixir. Maybe it's the same as F#, I don't know, but it automatically performs partial application, so that:

Enum.filter([1, 2, 3, 4], fn n -> n % 2 == 0 end)

also works like:

[1, 2, 3, 4] |> Enum.filter(fn n -> n % 2 == 0 end)


My library adds the piped value as the last parameter but if you read the docs, you'd see that you can use a placeholder to change its place:

    ppipe([1, 2, 3, 4]).pipe(Enum.filter, _, x => x % 2 === 0)
Not that filter fn in js is in the prototype of arrays so you'd need to extract it. There's a shortcut when using my library though:

    ppipe([1,2,3]).filter(x => x > 2)
I made the practical decision to provide prototype functions from the piped value while chaining.


This is nice syntax, but since there are two competing proposals and they're collectively at stage 1, this isn't something anyone should really be using for nontrivial code, since there's a good chance either way that you'll be writing JavaScript that will never be valid.


I know this proposal has been sitting around forever, but it really can't come fast enough imho. I don't know how to help show my support for its inclusion, however. Can anyone help elucidate?


I decided to answer my own question and visit the tc39 website. The lazy answer is that you must be part of a company that is willing to pay ~$70000 to have a voice, and have access to your companies representative to tc39. There are other categories of members (like Mozilla), but here's the short list of "Ordinary members." More info at https://www.ecma-international.org/memento/members.htm

If you are part of one of these companies and interested in this proposal, it'd be super cool to see who you need to talk to to influence this decision.

Facebook

Google

Hitachi

IBM

Intel

Konica Minolta

Microsoft

PayPal

Stripe, Inc.


It's worth reflecting on why the most popular language in the world has syntax so inflexible that a simple operator like this requires begging some ubercorp for a compiler change. Smalltalk had user-definable binary/infix operators in 1980. They didn't require you to muck around with the compiler at all.

Ironically, JavaScript has the extensibility mechanisms and object-oriented features inspired by Smalltalk that could allow for pretty much the same thing. However, most of the community doesn't seem to even realize that this is a possibility.


Yeah! As a hobbyI much around with scheme (mostly), Smalltalk (a couple of times a year) and factor (maintaining some old tools I wrote in it).

The result is that if I whine, I whine about the choice of language, not about how a particular part of a language sucks. Extensibility like that should be built in, and then probably mostly discouraged. If you really need to scratch an itch, you will be able to do so without resorting to what usually ends up as ugly, non-composable hacks.


It's worth saying that mostly the same companies are building WebAssembly as a solution to this problem.


Really? If you would try to do an effective Smalltalk implementation in WebAssembly, you would immediately see that WebAssembly is making things even much worse.


It's wasm. It could be written in Hindi numerals and unicorn emojis for all I care. So long as llvm can make sense of it.


You can go talk to TC39 delegates in #tc39 on freenode right now (it will be kind of quiet for the next few days though since there is an in-person plenary and people are not looking at IRC as much). We also use https://es.discourse.group/ for async communication.

Also, many entities listed on Ecma's membership page are not there for TC39, and talking to people working for them will not get you anywhere.


According to the article, and companion Babel blog ( https://babeljs.io/blog/2019/07/03/7.5.0 ), this is usable already in combination with Babel.

Now, I understand how it might be desirable to NOT having to use Babel to run code, especially on Node. On the other hand, some amount of transpilation has become inevitable for js in the browser and I don't see this need going away soon, as JS evolves much quicker than browser adoption.


Yes, I know...but I live in TypeScript world where functional styles and types go together especially wonderfully. Babel solutions to TypeScript are messy and in some cases break the things I like about TypeScript. At least last I checked. But getting native TypeScript support is dependent on the feature actually making it into the official spec.


Been through that here, trying to get my whole dev env working nicely with both pipes and types. Very painful. I was surprised and a bit disappointed that pipelines seemed to be progressing years ago, then suddenly stalled. Nullish and optional chains are in ts already but pipelines aren't even on the horizon.

From what I hear from tc39 people, method extraction is one of the biggest must-haves holding this thing up. They want a nice way to bind contexts to an entry in the pipe. Ironically the difficulty of figuring this out for JS is keeping pipes out of TS, but it wouldn't be that hard a problem in TS which has ways to treat `this` as special already.

That, and the many competing proposals we already have. I'm fine doing an incremental rollout that doesn't need the placeholder `?` right now.


If I recall correctly, it's because the proposal[1] was last presented to committee in March 2018[2], and since then it's been essentially deadlocked as the committee members can't agree upon an "f#" style operator vs a "hack" style operator vs a "smart mix" of the two. Personally, I hate placeholder sigils and magic "context" variables (and hope they have no place in JS), so a simple "left operand becomes first argument in call to right operand" is all I'd really want.

[1] https://github.com/tc39/proposal-pipeline-operator [2] https://github.com/tc39/notes/blob/master/meetings/2018-03/m...


Same - the context variables are really for method extractions (e.g. the many times people used to call Array.prototype.slice on something). It's be easy in TS since `this` is already treated as special in function-arg contexts.


I loved it, but came to an impasse when I had to decide between pipeline and TS. For a while I was running both a babel and TSC watch, where my babel build dir fed into the TSC watch. Dealing with two daemons when one/both screwed up, and needing to read errors in transpiled code, was annoying. What decided it was being unable to run prettier, since I could either use the babel parser for pipelines, or the tsc parser for the types. I'd be very happy if we could put pipelines back in and not break our tools and tax the two cores on my MacBook Air to the limit.


I always wondered why does it take such a long time to formalize this in Babel's specs. It seems something fairly unequivocally useful, but I guess its implementation has some major hurdles in JS.


Babel doesn't have a spec per se. They try to be compliant with the ECMAScript spec, but implement popular proposals behind presets or plugins. You can read about their relationship to ECMAScript and the TC39 committee on their blog: https://babeljs.io/blog/2018/08/27/7.0.0#babels-role.

Getting something through the TC39 is a pretty lengthy process, you can read about it here https://tc39.es/process-document/.


It’s been so long I forgot that this proposal even existed


F# pipeline operator aka... function composition that has existed in other MLs forever?


Value of your comment being? It does not matter where it comes from, just that is cool, useful, readable and it might be a great idea to add it to JS, same as it is already in f#, oCaml, Elixir, etc etc etc.


I miss it when it's not there.

This is currently #5 on HN: https://www.infoq.com/articles/java-14-feature-spotlight

It's a pretty long read and could have done with some precedent.


For better or for worse, being first doesn't amount to much in real life. Being the first to mass adoption, that does matter.


It's not first to mass adoption either lol


Well, I'd say that "mass adoption" means it's in one of:

Javascript, Java, Python, C, C++, C#, PHP, Swift†, Ruby†, SQL†, Go†, VBA†, TypeScript†, Kotlin† and that's about it.

The languages with a dagger (†) are either niche languages or tied to a very specific (albeit very popular) platform.

Which one of them has the pipeline operator, especially in widespread use in libraries in its ecosystem? Personally, I don't know of any.

I'm not saying that what this blog post presents has achieved mass adoption, I'm just arguing that the pipeline operator for standard programming languages hasn't reached mass adoption, anywhere. I don't count Unix pipes as being the same thing as the pipeline operator, in this context ;-)


Probably the only mainstream language with mass pipeline usage is elixir. (And before you call it niche, pagerduty uses elixir, so your stack probably depends on it right now). Almost every significant code file of mine uses the pipe operator, and it's absolutely invaluable for debugging.


That's not really a good argument against calling it niche. A lot of numerical computing done from Python is actually run through Fortran and some of your OS is almost certainly written in raw assembly language. That doesn't mean that they aren't niche.


It's reverse order from normal composition, which is really handy.


Unfortunately good ideas take a long time to catch on in our industry!


I enjoy the utility of this operator in Elixir but I'm not sure how I feel about including it in JS.

My fear is that JS is trying to be all things to all developers- it feels like we just added the `class` keyword for the OO inclined and now we're moving towards functional programming? Do we really want to move further away from the idea of idiomatic JS?

But who knows, maybe it will open the door to massive productivity and enjoyability gains with the language...


Classes require tons of work under the hood to work. In exchange, they add a confusing new concept of fields alongside protypes while also hiding the actual prototype inheritance to ensure future confusion.

The pipe operator is a super-basic transformation of the AST. In exchange, you get much easier to read "nested" functions. `foo(bar(baz(x))) --> x |> baz |> bar |> foo` is very easy to teach and very easy to understand.

It's hardly a fair comparison.


Can't help but wonder about the maintainability of JS codebases with all this additional stuff, particularly with things that aren't part of the language spec like the pipe operator.


What was the author thinking with that pointless meme image?


Oh my god, we should never allow people to have a little fun on their own blog and if they do, let’s call them childish and unworthy of finding work. /s

It’s just a meme, used correctly, what’s the problem? This isn’t the White House’s blog.


You said a lot of words that I did not say...

To me, that image was superfluous and likely to perpetuate the "boys club" appearance of tech.

Sure, the author can put whatever they want on their site. But why go to the trouble of writing something and then tossing in a Piss Off message to some of the readership?


Yes it comes across really unprofessional and adds nothing. It is really OK to not have any images in your blog post if it is is good!


if you add multiplication functionality, identity and termination symbols to it, you get something that is closer to a proper category like done in this: https://github.com/kummahiih/python-category-equations

f1(?) |> ( f2(?), I ) |> f3(?) == f1(?) |> f2(?) |> f3(?) , f1(?) |> f3(?)

it just feels natural that way


Fun fact: the `pipe` function from `rxjs` can be imported on its own where it will work on any value of type T, not just `Observable<T>`.


I wrote something similar for Scheme - https://cons.io/reference/sugar.html#chain. It's interesting to see that other people came up with a quite similar solution, providing two modes: pass-directly, pass-by-variable. Does the JS version supports destructuring?


I have a similar scheme thing, but that does implicit left or right insert when no <> is present and has an apply <...>:

https://bitbucket.org/bjoli/guile-threading-macros/src

I wrote it when I was a beginner at syntax-rules macros, but it gets the job done. It doesnt do destructing though!


Your version is interesting too. For a while I also thought about the stop-when-false idea, but came to the conclusion that it should probably be another macro. Building an enough powerful, but easy macro, isn't that simple.


I think it's worth nothing that a full Promise handling pipe function can be written like so:

    const pipe = (...args) => args.reduce(async (acc, fn) => fn(await acc));

No actual third party libraries required


It's nice that you can't typo your variables when writing point-free code


For those of you who work with Node, highland.js is another great route for composable programming. It has excellent support for Node streams, which has made it quite handy with Gulp especially!


The 5 codebox does filter map reduce. That makes me wonder, is there any loop fusion going on in JS? Or are there any good libraries for transducers?


What happens when you need to debug and see how the data looks at each stage of the transformation pipeline? Won't the debugger show this as a single step if you use the pipeline operator? It does look cleaner and means you don't have to spend time coming up with names for the intermediate results though.


The pipeline is still a set of function calls, despite the syntax sugar. You can think of x |> f |> g as g(f(x)). Even when written as a single line, a good debugger will let step through each function one at a time (as that is effectively how the system operates), and a great debugger will help you set breakpoints even "inside the line" to a specific point.

(Many debuggers already support that last bit of inserting breakpoints inside of lines instead of "at" lines, it's just not as obvious as the usual "click the spot to the far left gutter of the line you want". Sometimes it is something like a Right-Click on the specific part of the line and look for a command such as "Insert Breakpoint Here". It's something to learn if your chosen debugger supports today.)

The obvious tangent here, of course, is that many times when you might want to heavily use something like a pipeline operator you are likely working with something more abstract between function calls like an Iterator/Generator pattern (or an Observable pattern), and debugging those require different habits/tools as the "intermediate results" aren't directly interesting (an Iterator is "just" an object with a next() function, it's not an array of intermediately processed data). Learning to debug those patterns is its own skillset (whether or not you are using a pipeline function or a pipeline operator or old-fashioned function call syntax), but one common example is a function you'd add inside the pipeline often called something like a "tap". In that case you might "tap" in to the middle of a pipeline to log intermediate results. Something like:

    x |> f |> tap(y => console.log(y)) |> g


foo() |> bar |> quax |> hotdog

You could always stick a breakpoint at the start of bar, quax or hotdog and see what was passed in?


Why not use the haskell bind operator? They're similar, right?


People seem to find |> easier to learn.


There is a proposal[0] but I'm afraid it's pretty much dead now.

[0] https://github.com/tc39/proposal-bind-operator/


closer to compose (.) than bind.

  f .  g = \x -> f (g x)

  f |> g = \x -> g (f x)


I think `|>` would actually be `&` in Haskell, it takes a value and a function.

`(&) :: a -> (a -> b) -> b`

https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-...


Haskell already has the function application operator, It's simply & in Haskell.


It's

    flip ($)


import Data.Function (&)


Thanks, I hate it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: