11 ms·
F# Pipeline Operator in JavaScript with Babel
- IggleSniggle 7y agoI 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?
- mirthflat83 7y agoIt’s been so long I forgot that this proposal even existed
- galacticdessert 7y agoAccording to the article, and companion Babel blog ( https://babeljs.io/blog/2019/07/03/7.5.0 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.
- IggleSniggle 7y agoYes, 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.
- jkoudys 7y agoBeen 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.
- hopia 7y agoI 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.
- andersns 7y agoBabel 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 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/ https://tc39.es/process-document/.
- IggleSniggle 7y agoI 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 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.
- gambler 7y agoIt'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.
- TomMarius 7y agoIt's worth saying that mostly the same companies are building WebAssembly as a solution to this problem.
- jkoudys 7y agoIt'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.
- xkriva11 7y agoReally? If you would try to do an effective Smalltalk implementation in WebAssembly, you would immediately see that WebAssembly is making things even much worse.
- wwwigham 7y agoIf 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 https://github.com/tc39/proposal-pipeline-operator [2] https://github.com/tc39/notes/blob/master/meetings/2018-03/mar-22.md#10ive-pipeline-operator https://github.com/tc39/notes/blob/master/meetings/2018-03/m...
- jkoudys 7y agoSame - 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.
- jkoudys 7y agoI 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.
- egeozcan 7y agoI 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 https://github.com/egeozcan/ppipe (needs ts typings though)
- svrtknst 7y agoThis 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)
- egeozcan 7y agoMy 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.
- alharith 7y agoThere'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/ https://www.javierchavarri.com/js_of_ocaml-and-bucklescript/
- hopia 7y agoAnd Elm & Purescript.
- aloisdg 7y agoAnd F# directly with Fable
- websharper 7y agoor WebSharper ...or Blazor
- KurtMueller 7y agoCan'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 :).
- platz 7y agoIt's nice that you can't typo your variables when writing point-free code
- WouterSpaak 7y agoFun 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>`.
- vmchale 7y agoF# pipeline operator aka... function composition that has existed in other MLs forever?
- cultus 7y agoIt's reverse order from normal composition, which is really handy.
- vmchale 7y agoHow tf is reversing order significant?? This is <&> in Haskell (it's in the standard library). Futhark has it in the prelude too.
- oblio 7y agoFor better or for worse, being first doesn't amount to much in real life. Being the first to mass adoption, that does matter.
- vmchale 7y agoIt's not first to mass adoption either lol
- oblio 7y agoWell, 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 ;-)
- 7y ago
- zackmorris 7y agoSomewhat 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-origin-of-variable-it-in-read-eval-print-loop/3342195 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/intro.html http://www.ugrad.math.ubc.ca/Flat/stack.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.
- vmchale 7y agoGets confusing in a multithreaded context. Or if you return pointers.
- smabie 7y agoI 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.
- vthriller 7y ago> 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))
- dunefox 7y ago
- kummappp 7y agoif 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 https://github.com/kummahiih/python-category-equations f1(?) |> ( f2(?), I ) |> f3(?) == f1(?) |> f2(?) |> f3(?) , f1(?) |> f3(?) it just feels natural that way
- seanwilson 7y agoWhat 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.
- LandR 7y agofoo() |> bar |> quax |> hotdog You could always stick a breakpoint at the start of bar, quax or hotdog and see what was passed in?
- WorldMaker 7y agoThe 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
- sbussard 7y agoWhy not use the haskell bind operator? They're similar, right?
- mrkeen 7y agocloser to compose (.) than bind. f . g = \x -> f (g x) f |> g = \x -> g (f x)
- ajuhasz 7y agoI 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-Function.html#v:-38- https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-...
- lgas 7y agoIt's flip ($)
- hopia 7y agoimport Data.Function (&)
- hopia 7y agoHaskell already has the function application operator, It's simply & in Haskell.
- adgasf 7y agoPeople seem to find |> easier to learn.
- oneshot-a4yukdk 7y agoThere is a proposal[0] but I'm afraid it's pretty much dead now. [0] https://github.com/tc39/proposal-bind-operator/ https://github.com/tc39/proposal-bind-operator/
- thibran 7y agoI wrote something similar for Scheme - https://cons.io/reference/sugar.html#chain 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?
- bjoli 7y agoI 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 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!
- thibran 7y agoYour 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.
- adgasf 7y agoI 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 ]);
- jiofih 7y agoThat fetch / await example is horrendous. Please don’t.
- lf-non 7y agoFor 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 https://github.com/ts-delight/pipe.macro
- lwb 7y agoAnyone else think the async/await example looks terrible? Why do the keywords have to be on their own line?
- waylandsmithers 7y agoI 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...
- goatlover 7y agoCan'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.
- hajile 7y agoClasses 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.
- TheAsprngHacker 7y agoWhy 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.html https://caml.inria.fr/pub/docs/manual-ocaml/libref/Stdlib.ht...
- wendyshu 7y agoYup
- markandrewj 7y agoI 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#.
- tpetricek 7y agoThe history of how the pipe operator got into F# is documented here: https://blogs.msdn.microsoft.com/dsyme/2011/05/17/archeological-semiotics-the-birth-of-the-pipeline-symbol-1994/ https://blogs.msdn.microsoft.com/dsyme/2011/05/17/archeologi...
- rattray 7y agoSounds 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 https://ocaml.org/releases/4.01.0.html
- vrotaru 7y agoIt 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/archeological-semiotics-the-birth-of-the-pipeline-symbol-1994 https://docs.microsoft.com/en-us/archive/blogs/dsyme/archeol...
- no_wizard 7y ago
- tombert 7y agoIt'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.
- bjoli 7y agoThose 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.
- tombert 7y agoNo 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.
- kazinator 7y agoI 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.
- blunte 7y agoWhat was the author thinking with that pointless meme image?
- mc3 7y agoYes 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!
- why-oh-why 7y agoOh 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.
- blunte 7y agoYou 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?
- dimgl 7y agoThanks, I hate it.
- koboll 7y agoThis 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.
- chaorace 7y agoFor 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!
- bjoli 7y agoThe 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?
- reaktivo 7y agoI 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