11 ms·
Show HN: A JavaScript function that looks and behaves like a pipe operator
- kszyh 3y agoNice idea, there is also @babel/plugin-proposal-pipeline-operator which would require much less changes in the code if pipeline operator is introduced in the future. But it requires working babel.
- caporaltito 3y ago)))))
- pharmakom 3y agoStill prefer pipe([ f, g, h, ])(123); Or, you know, a functional language.
- progx 3y agoIts all a problem of formatting, for me this is simple to read. const { status } = await send( capitalize(greeting) + "!" ) But i would not concat things in a method call, bad style. const message = capitalize(greeting) + "!" const { status } = await send(message) The example from the page is a little bit too simple. With complex operations a pipe-method would make more sense. But i will wait for the native pipeline-operator <https://github.com/tc39/proposal-pipeline-operator https://github.com/tc39/proposal-pipeline-operator> instead using now a function.
- richeyryan 3y agoI think the use of template strings simplifies it, even inline: const { status } = await send(`${capitalize(greeting)}!`) console.log(status) It does reduce the number of discrete operations to make the pipe example look more impressive, though.
- progx 3y agoBut you have to look at the whole string and check if send has a further parameter. With `send(msg)` you see it without taking extra time of reading. And let the compiler optimize your code, that is the job of a compiler. Write your code for humans.
- smilingemoji 3y agoThe following example code is a bit hard to read: const { status } = await send(capitalize(greeting) + "!") console.log(status) I disagree, I find this example code very easy to read because it reads like idiomatic Javascript. Unlike this library.
- Piisamirotta 3y agoI agree, I really don't understand this library
- knubie 3y agoI think the implication is that it's difficult to read because it's read non-linearly. You first need to start in the middle at `greeting`, then work your way out to `capitalize` on the left, then to `+ "!"` on the right, then work your way out one more layer to the left with `send`. Some kind of pipe operator can make this easier to read by arranging the operations linearly from left to right (or top to bottom as is the case with this library). Although in my opinion this library has its own readability issues.
- moritzwarhier 3y agoI agree. In the example case, I don't see why left-to-right or top-to-bottom would be more readable. Sure, it is good most of the time, but operator precedence rules exist for a reason. I don't want to read 2.chain(x => x * 2).chain(f) instead of f(2*x) These patterns have their place for async programming, pure FP, streams and more. They can also be a distraction. E.g. the library introduces its own promise-unwrapping semantics and more. I don't see the use for such a generic implementation, although I applaud the effort.
- madeofpalk 3y agoRight - because this library tries to fake a new language syntax feature without the actual support for it, it ends up being the worst of both worlds. Even ignoring the lines-of-code explosion, the non-standard use of parens/spacing makes it incredible difficult for me to parse. Props to the author for trying out something neat, but this is probably a bit to clever for my liking.
- deleted 3y ago[deleted]
- stby 3y agoI had a hard time reading the example until I applied the "normal" JS indentation, that would also likely be applied by Prettier and the likes. V(greeting, // initial value "hi" V(capitalize), // custom function call "Hi" V.concat("!"), // String method `concat` call "Hi!" V(send), // custom async function call Promise { <pending> } V.status, // automatic promise chaining + getting property Promise { 200 } V(console.log), // automatic promise chaining + global function call logs 200 ) It's certainly a nice usage of JavaScript's Proxy object and the resulting syntax is simple to grasp. Also glad to see the TS definitions in there. Well done!
- FroLeMageBlanc 3y agoAuthor here. Thanks for your nice comment. I added a note to show the "normal" autoformatted syntax.
- breakfastduck 3y agoI can see this being useful but boy does it look ugly
- pocketsand 3y agoPrepending doesn’t help its case. I like the R convention of pipe at end of line, indent on lines 2+
- notpushkin 3y agoInteresting! I usually do exactly the opposite when I can. E. g. in Bash I would rather do this: curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ | gpg --dearmor \ | sudo tee /etc/apt/keyrings/docker.gpg \ > /dev/null
- pocketsand 3y agoI suppose it's just what you're used to, of course. However, in R, you don't need to trailing slashes on new lines. Plus, their pipes are hideous: %>% or now |>. I do kind of like how the pipe delimiter looks on the left.
- _v7gu 3y agoHonestly, if prototype pollution was not a problem for optimization and other things I’d just toss a Object.prototype.pipeTo = function(f) { return f(this) }; And call it a day. It does about 80% of what a pipe macro would do, has instant compatibility with other prototype methods and leads to very simple types. The only issue is that you have to define lambdas to call multivariable functions. D has got this very, very right.
- Tade0 3y agoNowadays, as someone writing a library, you can work around prototype pollution via Symbols and let your users modify prototypes on their terms.
- Drakim 3y agoWould that mean that instead of obj.pipeTo() I'd have to call obj[PIPE_TO] or something like that?
- Tade0 3y agoEssentially. And then have: Object.prototype.pipeTo = Object.prototype[PIPE_TO] or some other name instead of `pipeTo` should there be a conflict. JS already has a few well-known symbols like Symbol.toPrimitive that allow one to modify an object's behaviour (in this case what happens when `valueOf` or `toString` is called), so there's precedent.
- mlajtos 3y agoExactly right! https://github.com/mlajtos/es1995 https://github.com/mlajtos/es1995
- mg 3y agoThis would be my preferred syntax: status = greeting+"!" ~> capitalize ~> send This would need 2 changes to JS: 1: ~> being a pipe operator 2: Calling an async function from within an async function implies await Without 2, it would look like this: status = greeting+"!" ~> capitalize ~> await send
- jmisavage 3y agoThere is a proposal to add the pipeline operator |> to the standard that works similar to what you have here. https://github.com/tc39/proposal-pipeline-operator https://github.com/tc39/proposal-pipeline-operator
- madeofpalk 3y agoWhich is basically what the proposal is https://github.com/tc39/proposal-pipeline-operator#this-proposal-hack-pipes https://github.com/tc39/proposal-pipeline-operator#this-prop...
- mg 3y agoThe difference is that they want to turn a = d(c(b,7)) into a = b |> c(%,7) |> d(%) and I would like to see it turn into a = b,7 ~> c ~> d
- gpderetta 3y agoDoes JS have a splat operator? So: a = (b, 7) |> c(*%) |> d(%) Too much operator soup I guess.
- notpushkin 3y agoYeah, it is the ellipsis: a = b |> c(...%, 7) |> d(%) Spaciousness helps here a bit, but still way too many syntax, I agree.
- IggleSniggle 3y agoIt took me a moment, but your version is very nice.
- creakingstairs 3y agoIf you are interested in this kind of things, also have a look at fp-ts and it’s version of pipe and flow. Also comes with various monads to play around with! [1] https://github.com/gcanti/fp-ts https://github.com/gcanti/fp-ts
- koromak 3y agoNot sure if lodash is still in fashion but I use their flow and fp_ functions often. I like the readability, and tree shaking still works.
- meandmycode 3y agoI mean, if you abuse formatting enough I suppose... although I will say, the pipeline/bind operator has been in proposal for a decade at this point.. it's depressing that some version hasn't shipped, because C# extensions and similar in swift can really help shift how from what.. it* could really help JavaScript a lot
- epolanski 3y agoThe JS proposal is very dubious imho, I'm really not fond of the direction they want to take the pipe operator.
- patates 3y agoI use ppipe: https://github.com/egeozcan/ppipe https://github.com/egeozcan/ppipe Looks tidier to my eyes const newPipe = ppipe.extend({ divide (x, y) { return x / y; }, log(...params) { console.log(...params); return params[params.length - 1]; } }); const res = await newPipe(10) .pipe(x => x + 1) .divide(_, 11) .log("here is our x: ") //logs "here is our x: 1" .pipe(x => x + 1) // 2
- corbezzoli 3y agoBut then you have to define each method in advance, it doesn’t look like the pipeline operator at all, but more like the old jQuery.fn.method shenanigans.
- patates 3y agoThat's not the case at all, have a look at the docs. The thing is pretty powerful.
- matheusmoreira 3y agoJust like Clojure's threading operator. (-> x (f) (g) (h)) https://clojure.org/guides/threading_macros https://clojure.org/guides/threading_macros
- snorremd 3y agoFor those that don't know. In Clojure it is also perfectly valid to drop the parantheses for each subsequent call in a threading macro if you don't want to pass in any additional arguments: (-> x f g h) If you need to pass an additional argument to g you can always do: (-> x f (g foo) h) There are thread first ->, thread last ->>, and even a thread as "as->" depending on where you want to place the argument when you pipe the result through the thread of functions.
- barisx 3y agoIt is Vavascript.
- Tade0 3y agoI like goofy projects like this one but I think that if you insist on having calls there, then might as well make it more explicit with something like this: pipe((p) => ([ "hello", p.concat("!"), send, p.status, console.log ])); Where `p` stands for Proxy to the previous result. Under the hood it would just return an object describing the name of the called method and arguments passed. The pipe function would then iterate over the array, calling methods as described. It's not immediately clear what should happen when a method's return type is a function, but I suppose this can be handled via convention.
- Tade0 3y agoHere's a very basic implementation: https://github.com/Tade0/pipe/blob/master/pipe-sync.js https://github.com/Tade0/pipe/blob/master/pipe-sync.js
- xigoi 3y agoNice idea, but something as simple as this doesn't work: pipeSync(p => [ "hello", p + " world", console.log ]);
- deleted 3y ago[deleted]
- pineconewarrior 3y agoGulp[1] and node streams sortof do this gulp.src(config.src) .pipe(uglify()) .pipe(gulp.dest(config.dest)) .pipe(size()); [1] https://www.npmjs.com/package/gulp https://www.npmjs.com/package/gulp
- letmeinhere 3y agoI've used the Remeda package to preserve type information in pipes in Typescript: https://remedajs.com/ https://remedajs.com/. There may be better implementations (fp-ts?), but I like Remeda's docs.
- whatatita 3y agoThanks for linking Remeda! I also dislike the docs for fp-ts and that's killed adoption.
- anoy8888 3y agoI don’t like the syntax . But pipe operator concept is great for helping you think about your program as a series of data transformation steps . I just write my simple pipe function and use it . No need for library .
- pgt 3y agoLisper screaming See Clojure macros for thread-first `->`, thread-last `->>`, thread-as `as->`, `some->`, `some->>` and `cond->`: https://clojure.org/guides/threading_macros https://clojure.org/guides/threading_macros You can leave all this hurt behind you and use ClojureScript, which compiles to JavaScript.
- pgt 3y agoExample: (->> (range 10) ;; (0 1 2 3 4 5 6 7 8 9) (filter even?) ;; (0 2 4 6 8) (map inc) ;; (1 3 5 7 9) (apply +)) ;; 25 => 25 which macroexpands to: (apply + (map inc (filter even? (range 10))))
- joisig 3y agoThe "vertical" example looks contrived in JavaScript, but if that's your thing, I think it's OK. But please don't promote antipatterns from languages that actually have a pipe operator (like Elixir) such as the '''concat("!")''' where the previous implementation's version using an operator is much clearer and more idiomatic. I see a ton of Elixir code where people shoe-horn things into a vertical pipeline where the "normal" code would be a lot more readable, for example invoking '''Kernel.+(2)''' instead of just doing '''my_var + 2'''
- keepamovin 3y agoThis is cool, I love JS syntax hacks. Well done! const { status } = await send(capitalize(greeting) + "!") console.log(status) I find that easy to read, and your pipe operator harder (but still sensible!). I guess that just reflects the backgrounds we come from, people from more functional backgrounds (maybe lisp or Haskell) will find the latter example easier I guess!
- FroLeMageBlanc 3y agoAuthor here. Thanks! Yes I have to confess I have both a Scheme and Elm background ;)
- epolanski 3y agoHow's it different from other `pipe` implementations around, e.g. the one from `fp-ts`? https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe
- FroLeMageBlanc 3y agoThe pipe function of `fp-ts` doesn’t have direct access to methods and properties of the pipe output like Verticalize. You have to wrap them into anonymous functions. Same with promises.
- epolanski 3y agoYou can always pipe( 2, double, x => x.toString() ) I love your approach too, it's really more of a pipe operator than a `pipe` function which expects the pipeline to happen on the arguments side.
- izoow 3y agoReminds me of something I tried hacking together in python for fun from functools import partial class Pipeable: def __init__(self, fn): self.fn = fn def __ror__(self, lhs): return self.fn(lhs) def pipeable(fn): return lambda *args: Pipeable(partial(fn, *args)) filter = pipeable(filter) map = pipeable(map) list = pipeable(list) sum = pipeable(sum) min = pipeable(min) max = pipeable(max) any = pipeable(any) # Usage: range(1, 100) | filter(lambda x: x < 50) | max() # 49 [1, 2, 3, 4] | filter(lambda x: x % 2 == 0) | map(lambda x: x * 3) | list() # [6, 12] [1, 2, 3, 4] | map(lambda x: x >= 5) | any() # False
- porridgeraisin 3y agoexport const pipe = <A, B>(f: Fun<A, B>) => { return { to: <C>(g: Fun<B, C>) => pipe((arg: A) => g(f(arg))), build: () => f, }; }; Much simpler alternative. const process = pipe(readLines).to(x => cut(x, 2)).to(....).build() const result = await process(fd) P.S the Fun type is just (...args: A) => B
- jeroenhd 3y agoThis solution seems a lot cleaner to me syntactically, but it does lack helpers like .concat(). You'd have to write methods like .pipe( x => x + "!") to extend methods. Then again, prefixing and postfixing operaties shouldn't be too hard to write either
- porridgeraisin 3y agoI like https://github.com/WiseLibs/wise-river https://github.com/WiseLibs/wise-river For complex uses
- amadeuspagel 3y agoWhen in rome ...
- guntherhermann 3y agoNot sure if other people have mentioned this, but Ramda's `pipe` operator is left-to-right function composition which has a similar effect to this. I use it regularly, I find it very useful. https://ramdajs.com/docs/#pipe https://ramdajs.com/docs/#pipe
- jsdeveloper 3y agobravo! loved it. I am so eagerly waiting for pipe function. your take on it meanwhile looks good to me.
- FroLeMageBlanc 3y agoThanks! I’m currently trying to find a better TS type but that’s a bit hairy...
- ranting-moth 3y ago>The following example code is a bit hard to read: >const { status } = await send(capitalize(greeting) + "!") >console.log(status) No it's not hard to read. >Make it less nested, more vertical, by using the V "pipe": >V( greeting, // initial value "hi" >V (capitalize), // custom function call "Hi" >V .concat("!"), // String method `concat` call "Hi!" >V (send), // custom async function call Promise { <pending> } >V .status, // automatic promise chaining + getting property Promise {200} >V (console.log), // automatic promise chaining + global function call logs 200 ) This cursed abomination is a joke isn't it?
- FroLeMageBlanc 3y agoIs functional programming a joke? YMMV...
- guntherhermann 3y agoI don't think the first example is less pure than the second, what makes you think it is?
- FroLeMageBlanc 3y agoPurely functional programming is a subset of functional programming (see F# for instance). I was referring to the pipe operator syntax common to many functional programming languages like Haskell, F#, OCaml, Elixir, Elm and so on...
- guntherhermann 3y agoOk, you aren't arguing for 'pure' functional programming, but I think you have sidestepped my question, then: The pipe operator is just one part of functional programming, composing and applying functions `f(g(x))` is still functional, and I would argue it exactly what is happening in the first example.
- 3y ago
- bacza2 3y agoFinally, now it's easy to read code on a phone
- reverseblade2 3y agoHow about just use F# and https://fable.io https://fable.io
- tamimio 3y agoI love it!! Innovative idea, love webdevs ranting in the comments too, how dare you threaten their precious abominations called JavaScript!?
- FroLeMageBlanc 3y agoHaha thanks! Yes haters gonna hate. If you like it, use it; if you don’t, simply don’t use it ;)
- agumonkey 3y agoJust wait a few years and most dynlangs will have a pipe operator built-in.
- rkeene2 3y agoI created something similar for Tcl called pipethread [0] [1], but using something closer to the pipe syntax. It has a lot of interesting features that would be difficult to replicate in JavaScript. [0] https://wiki.tcl-lang.org/page/pipethread https://wiki.tcl-lang.org/page/pipethread [1] http://www.rkeene.org/tmp/pipethread-presentation-withnotes.pdf http://www.rkeene.org/tmp/pipethread-presentation-withnotes....
- newtom 3y agoProposal from 2015 to add pipes to JS: https://github.com/tc39/proposal-pipeline-operator https://github.com/tc39/proposal-pipeline-operator
- nsonha 3y agoit could have been V(greeting, capitalize, ['concat', "!"], send, 'status', console.log ) but at that point it's already Ramda or smt and doesn't look as funky
- mrcwinn 3y agoI jokingly thought to myself, when do we just move to Elixirscript? And then, remembering that everything has already been invented, I found this: https://github.com/elixirscript/elixirscript https://github.com/elixirscript/elixirscript You're welcome.
- tqwhite 3y agoThis is cool and, if it interests you, you might like a thing I have been using for many years. https://www.npmjs.com/package/qtools-asynchronous-pipe-plus https://www.npmjs.com/package/qtools-asynchronous-pipe-plus
- kemiller2002 3y ago[ fn1, fn2, fn3, fn4 ].reduce((s, i) => i(s), starting_value); Same thing. Less code, standard functions.
- tills13 3y agothis is all fun and games until prettier turns it into an absolute mess.
- luxuryballs 3y ago90% of the time a little weed enhances coding productivity and solution clarity, the other 10% however…
- denvaar 3y agoI think what makes a programming language easy to use is largely due to the constraints that it imposes, through carefully thought-out design decisions. At first the constraints may be difficult to grasp. You may feel like a feature is missing, but that's part of learning a language. Once you have learned them, the constraints actually become helpful. One thing I dislike about JS is that it's like the wild west. You have a mash-up of all different paradigms that are possible, so you get libraries like this. No, I wouldn't say this makes code more readable, though I'm a fan of the pipe in other languages. It just makes yet another way to do the same thing. I don't mean to be rude or ranty, so with that said, it's cool that you were able to make this. Kudos for that.
- lcnPylGDnU4H9OF 3y ago> I think what makes a programming language easy to use is largely due to the constraints that it imposes, through carefully thought-out design decisions. That's interesting. I'm pretty well convinced of the opposite. I primarily work with Ruby, which doesn't really have a whole lot of language constraints in the context of "what can I do?". Part of the language design philosophy of Ruby is to give developers "sharp knives" and let them individually avoid chopping off their own fingers. I haven't tried my self but I would expect that something similar or identical to this could be implemented in Ruby and I wouldn't consider that to be something which makes it worse. Indeed, that's one of my favorite features of the language.
- deleted 3y ago[deleted]
- rqtwteye 3y agoJust looked at the examples. To me every one of them makes the original code less readable. I would also encourage people to not get used to this style because now you are stuck on some weird JS style and will have trouble reading code in other languages.
- jtolmar 3y agoIs this just greeting = capitalize(greeting); greeting = greeting.concat("!"); greeting = await send(greeting); greeting = greeting.status; console.log(greeting); but with less accurate breakpoints?
- Nijikokun 3y agoyes
- CodeWriter23 3y agoParaphrasing: this code is unreadable so let me show you some code that is even harder to read.