8 ms·
Working pipe operator today in pure JavaScript
- koito17 1y agoSince this library leverages Symbol.toPrimitive, you may also use operators besides bitwise-OR. Additionally, the library does not seem to dispatch on the `hint` parameter[0]. Now I want to open a JS REPL, try placing this library's pipe object into string template literals, and see what happens. Overall, cool library. [0] https://tc39.es/ecma262/multipage/abstract-operations.html#sec-toprimitive https://tc39.es/ecma262/multipage/abstract-operations.html#s...
- bryanrasmussen 1y agowhat are your ideas regarding the pipe object in string template literals? I'm just looking for an overview to see if it sparks some ideas.
- bckr 1y agoVery appealing.
- globalhsbc 1y ago[dead]
- stephenlf 1y agoCool work!
- c249709 1y agothis is sick
- tinyspacewizard 1y agoSad that the pipe operator proposal seems to have stalled. The F# version of the proposal was probably the simplest choice.
- pavlov 1y agoFurther proof that JavaScript accidentally became the new C++. “Aren’t you surprised that this syntax works?” is not praise for a language design.
- asah 1y agoAgreed!!!! Serious q: but how does this sentiment change with LLMs? They can pickup new syntax pretty fast, then use fewer tokens...
- fph 1y agoI imagine the error messages must be terrible to read, since this hack is based on reusing syntax that was meant for something entirely different.
- Cthulhu_ 1y agoIt sounds like using less tokens (or, less output due to a more compact syntax) is like a micro-optimization; code should be written for readability, not for compactness. That said, there are some really compact programming languages out there if this is what you need to optimize for.
- pavlov 1y agoI’ve heard it said before on HN that this is not true in general because more tokens in familiar patterns helps the model understand what it’s doing (vs. very terse and novel syntax). Otherwise LLMs would excel at writing APL and similar languages, but seems like that’s not the case.
- throwawaymaths 1y agoprobably because there arent enough apl examples to imbue the rare weird apl tokens with sufficient semantic meaning to be useful.
- miningape 1y agoIMO it's more likely to get confused because there are less unique tokens to differentiate between syntax (e.x. pipe when we want bitwise-or or vice-versa)
- goobert 1y agoNice! I love it when a language introduces new syntax for things that weren't remotely difficult in the first place!
- Ciantic 1y agoFirst example doesn't work though: const greeting = pipe('hello') | upper | ex('!!!') await greeting.run() // → "HELLO!!!" If you look at the tests file, it needs to be written like this to make it work: let greeting; (greeting = pipe('hello')) | upper | ex('!!!'); await greeting.run(); Which is not anymore as ergonomic.
- byteknight 1y agoSeems similar to the problem encountered when making the stupid idea PyNQ: https://github.com/IAmStoxe/PyNQ https://github.com/IAmStoxe/PyNQ
- md224 1y agoI suspect this was written with an LLM and the author didn't actually verify that the examples in the README worked.
- deleted 1y ago[deleted]
- dymk 1y agoRecently, I ripped usage examples out of a rust project's README.md, and put them in doc comments. Almost all of them were broken due to small changes over time, and I never remembered to update the readme. `cargo test` runs doc comments like mini integration tests, so now the examples never rot. I wish more languages and tools had this feature. It means having to go to the linked docs (which are automatically pushed to the repo's github pages) to see examples, but I think this is a reasonable tradeoff.
- Thom2000 1y agoFWIW it's possible to run readme examples automatically add part of tests: https://github.com/parallaxsecond/rust-cryptoki/blob/main/cryptoki/src/lib.rs#L3 https://github.com/parallaxsecond/rust-cryptoki/blob/main/cr...
- 1y ago
- sproutini 1y agoOverengineered in my view, what is wrong with `x | f` is `f(x)`? Then `x | f | g` can be read as `g(f(x))` and you're done. I don't see any reason to make it more complicated than that.
- xigoi 1y agoYou can’t make it work like that in current JavaScript.
- deleted 1y ago[deleted]
- flanked-evergl 1y agoIt would be nice to have well-maintained fluent/pipe/streaming API solution for Python.
- reverseblade2 1y agoAlternatively just use F# and Fable
- jappgar 1y agois this solving a problem people actually have? other libraries like rxjs use .pipe(f,g,h) which works just fine.
- whizzter 1y agoFully agreed, var-arg functions are well established in JS so no need to abuse operators for these kinds of things.
- whizzter 1y agoThis kind of stuff is why C++ developers has an almost overly allergic reaction to operator overloading.
- jagged-chisel 1y agoC++ is the reason people have that reaction. The quintessential example in introductory texts for operator overloading is using bit-shift operators to output text. I mean, come on - if that’s your example, don’t complain when people follow suit and get it wrong.
- whizzter 1y agoC++ has std::format these days that does a far more sane thing, people are too quick to throw out the baby with the bathwater when it comes to bad things. Some OO is fine, just don't make your architecture or language entirely dependent on it. Same with operator overloading. When it comes to math heavy workloads, you really want a language that supports operator overloading (or have a language full of heavy vector primitives), doing it all without just becomes painful for other reasons. Yes, the early C++ _STDLIB_ was shit early on due to boneheaded architectural and syntactic decisions (and memory safety issues is another whole chapter), but that doesn't take away that the language is a damn powerful and useful one.
- zamadatix 1y agostd::format in C++20 is just for the string manipulation half but you still left shift cout by the resulting string to output text in canonical C++. C++23 introduced std::print(), which is more or less the modernized printf() C++ probably should have started with and also includes the functionality of std::format(). Unfortunately, it'll be another 10 years before I can actually use it outside of home projects... but at least it's there now!
- 1718627440 1y agoWhile that operator is also used for bit-shift, it is not the bit-shift operator. It's not that the bit-shift operator is used for stream direction, it's that the same operator is used for both stream direction and bit-shifts. And which code is operating on both high-level abstract streams and bit-shifts at the same time.
- keepamovin 1y agoDamn, that’s really clever. I love seeing these expressive explorations of JavaScript syntax.
- keepamovin 1y agoDamn, that’s really clever. I love seeing these expressive explorations of JavaScript syntax.
- gregabbott 1y agoIn case it might interest anyone, I wrote a similar vanilla JS function last year called Chute. Chute chains methods and function calls using dot-notation. https://github.com/gregabbott/chute https://github.com/gregabbott/chute
- suspended_state 1y agoThat's Point-free style programming. https://en.wikipedia.org/wiki/Tacit_programming https://en.wikipedia.org/wiki/Tacit_programming
- xixixao 1y agoWon’t work in TS. I would actually love extension of TS with operator overloading for vector maths (games, other linear algebra, ML use cases). I wouldn’t want libraries to rely on it, but in my own application code, it can sometimes be really helpful.
- CharlieDigital 1y agoCheck out C#. CliWrap does exactly this: https://github.com/Tyrrrz/CliWrap/blob/master/CliWrap/Command.PipeOperators.cs https://github.com/Tyrrrz/CliWrap/blob/master/CliWrap/Comman... // Examples var cmd = Cli.Wrap("foo") | (stdOut, stdErr); var target = PipeTarget.Merge( PipeTarget.ToFile("file1.txt"), PipeTarget.ToFile("file2.txt"), PipeTarget.ToFile("file3.txt") ); var cmd = Cli.Wrap("foo") | target;
- juliend2 1y agoThis cargo seem to give magical superpowers.
- MathMonkeyMan 1y agoNeat, but I think that functions already do what we need. For one thing, the example isn't the most compelling, because you can: const greeting = 'hello'.toUpperCase() + '!!!'; or const greeting = 'HELLO!!!'; That said, there is already: function thrush(initial, ...funcs) { return funcs.reduce( (current, func) => func(current), initial); } const greeting = thrush('hello', s => s.toUpperCase(), s => s + '!!!');
- nonethewiser 1y agoAre any of the cases compelling? Thinking of the actual proposal. It creates some new magic with |> and % just for syntactic sugar.
- accrual 1y agoI am all for clean syntax but I feel like JS has already reached a nice middle ground between expressiveness (especially w/ map/reduce/filter) and readability. I'd personally rather not have another syntax that everyone will have to learn unless we're already moving to a new language.
- nonethewiser 1y agoI agree but to steelman it, what about custom functions? I think just doing it naively is perfectly fine. Or if you want use some pipe utility. Or wrap the array, string, etc. with your own custom methods.
- IshKebab 1y agoI think JS's map/reduce/filter design is one of the worst ones out there actually - map has footguns with its extra arguments and everything gets converted to an array at the drop of a hat. Still, pipeline syntax probably won't help fix any of that.
- eyelidlessness 1y ago> everything gets converted to an array at the drop of a hat Can you name an example? IME the opposite is a more common complaint: needing to explicitly convert values to arrays from many common APIs which return eg iterables/iterators.
- fergie 1y agoPipes are great in environments where "everything is a string" (bash, etc), but do we really need them in javascript? I have yet to see a compelling example.
- tinyspacewizard 1y agoPipes are great where you want to chain several operations together. Piping is very common in statically typed functional langauges, where there are lots of different types in play. Sequences are a common example. So this: xs.map(x => x * 2).filter(x => x > 4).sorted().take(5) In pipes this might look like: xs |> map(x => x * 2) |> filter(x => x > 4) |> sorted() |> take(5) In functional languages (of the ML variety), convention is to put each operation on its own line: xs |> map(x => x * 2) |> filter(x => x > 4) |> sorted() |> take(5) Note this makes for really nice diffs with the standard Git diff tool! But why is this better? Well, suppose the operation you want is not implemented as a method on `xs`. For a long time JavaScript did not offer `flatMap` on arrays. You'll need to add it somehow, such as on the prototype (nasty) or by wrapping `xs` in another type (overhead, verbose). With the pipe operator, each operation is just a plain-ol function. This: xs |> f Is syntactic sugar for: f(xs) This allows us to "extend" `xs` in a manner that can be compiled with zero run-time overhead.
- discomrobertul8 1y agoif the language or std lib already allows for chaining then pipes aren't as attractive. They're a much nicer alternative when the other answer is nested function calls. e.g. So this: take(sorted(filter(map(xs, x => x \* 2), x => x > 4)), 5) To your example: xs |> map(x => x \* 2) |> filter(x => x > 4) |> sorted() |> take(5) is a marked improvement to me. Much easier to read the order of operations and which args belong to which call.
- nonethewiser 1y agoFirst of all, with the actual proposal, wouldnt it actually be like this? with the %. xs |> map(%, x => x * 2) |> filter(%, x => x > 4) |> sorted(%) |> take(%, 5); Anything that can currently just chain functions seems like a terrible example because this is perfectly fine: xs.map(x => x * 2) .filter(x => x > 4) .sorted() .take(5) Not just fine but much better. No new operators required and less verbose. Just strictly better. This ignores the fact that sorted and take are not actually array methods, but there are equivalent. But besides that, I think the better steelman would use methods that dont already exist on the prototype. You can still make it work by adding it to the prototype but... meh. Not that I even liket he proposal in that case.
- nymalt 1y agoThat's clever! But I still want JS to get the actual pipeline operator.
- bonquesha99 1y agoIf you're interested in the Ruby language too, check out this PoC gem for an "operator-less" syntax for pipe operations using regular blocks/expressions like every other Ruby DSL. https://github.com/lendinghome/pipe_operator#-pipe_operator https://github.com/lendinghome/pipe_operator#-pipe_operator "https://api.github.com/repos/ruby/ruby".pipe do URI.parse Net::HTTP.get JSON.parse.fetch("stargazers_count") yield_self { |n| "Ruby has #{n} stars" } Kernel.puts end #=> Ruby has 15120 stars [9, 64].map(&Math.pipe.sqrt) #=> [3.0, 8.0] [9, 64].map(&Math.pipe.sqrt.to_i.to_s) #=> ["3", "8"]
- dominicrose 1y agoIt's an interesting experiment but standard Ruby is expressive enough. [9, 64].map { Math.sqrt(_1) } #=> [3.0, 8.0] For the first example I would just define a method that uses local variables. They're local so it's not polluting context.
- pwdisswordfishy 1y agonew Proxy(function(){}, { get(_, prop) { if (prop === Symbol.toPrimitive) return () => ... As opposed to, you know, just defining a method. Proxy has apparently become the new adding custom methods to built-in prototypes.
- rco8786 1y agoVery clever. Love seeing stuff like this that pushes the bounds
- sethcalebweeks 1y agoI love the idea! The creativity of (ab)using JavaScript type coersion is really neat. I did something similar using proxies to create a chainable API. https://dev.to/sethcalebweeks/fluent-api-for-piping-standalone-functions-in-javascript-329c https://dev.to/sethcalebweeks/fluent-api-for-piping-standalo... const shuffle = (arr) => arr.sort(() => Math.random() - 0.5); const zipWith = (a, b, fn) => a.slice(0, Math.min(a.length, b.length)).map((x, i) => fn(x, b[i])); const log = (arr) => { console.log(arr); return arr; }; const chain = chainWith({shuffle, zipWith, log}); chain([1, 2, 3, 4, 5, 6, 7, 8, 9]) .map((i) => i + 10) .log() // [ 11, 12, 13, 14, 15, 16, 17, 18, 19 ] .shuffle() .log() // e.g. [ 16, 15, 11, 19, 12, 13, 18, 14, 17 ] .zipWith(["a", "b", "c", "d", "e"], (a, b) => a + b) .log() // e.g. [ '16a', '15b', '11c', '19d', '12e' ] [0]; // e.g. '16a'
- deleted 1y ago[deleted]
- gregabbott 1y agoIn another comment, I mentioned a vanilla JavaScript function I published in 2024 called Chute. https://github.com/gregabbott/chute https://github.com/gregabbott/chute In a similar way to the featured project, Chute also uses proxies to work like a pipeline operator. But like in your reply, Chute uses a dot-notation style to chain and send data through a mix of functions and methods. You might like to see how Chute uses proxies, as it requires no `chainWith` or similar setup step before use. Without setup, Chute can send data through global or local, top-level or nested, native or custom, unary, curried or non-unary functions and methods. It gives non-unary functions the current data at a specific argument position by using a custom-nameable placeholder variable. The Chute page describes some more of its features: https://gregabbott.pages.dev/chute/ https://gregabbott.pages.dev/chute/
- don_searchcraft 1y agoThese TC39 proposals take way too long to get approved and implemented.
- user____name 1y agoIs this intended for code golf or something? This buys you literally nothing and just makes the language needlessly cryptic.
- ilaksh 1y agoSee also https://livescript.net/ https://livescript.net/
- aprilnya 1y agoThis is just different syntax for nesting function calls (i.e. c(b(a(value))) becomes value | a | b | c), right? Definitely would make code more readable if this was just something in JS or a compiler where it’s the same as normally calling functions.
- urvader 1y agoI'm not sure this is at all a good idea but thanks to the great discussion here at Hacker News - it is now up on npm: npm i aspipes
- adamddev1 1y agoNow we just need 'do notation' for monads! :-)
- mlajtos 1y agoPipe "operator" for the rest of us: Object.prototype.pipe = function(fn) { return fn(this) } 'hello'.pipe(upper).pipe(ex('!!!')) Or code golf version: Object.prototype.P=function(...F){return F.reduce((v,f)=>f(v),this)} 'hello'.P(upper,ex('!!!'))
- taylorallred 1y agoPiping syntax is nice for reading, but it's hard to debug. There's no clear way to "step through" each stage of the pipe to see the intermediate results.
- rokob 1y agoThere is usually some variant of tee that lets you do that.
- bitwize 1y agoIt's simultaneously a miracle and deeply wrong that this worked.
- tacone 1y agoI know that it's wrong but I love it. I am wondering if it could be useful for libraries: grid.columns.name.format(v => v | trim | truncate | bold) form.fields.name.validate(v => v | trim | required | email)