61 ms·
Pipe Operator (|>) For JavaScript
- charles_f 4y ago> three(two(one(value))) const oned = one(value); const twoed = two(oned); const threed = three(twoed); This proposition goes out of its way to find problems with code that is written in a confusing and uncommon way in the first place.
- substation13 4y agoIt's annoying to have to decide on and write out so many names. The intermediary names are not relevant to solving the problem. This is so much less noisy: value |> one |> two |> three
- vikingerik 4y agoThe intermediary names are extremely relevant to the next poor sucker who has to understand what you were trying to do. Code is read far more than it is written. Use temporary variables. Put in the effort to name them once, and then that effort pays back every time anyone needs to read and understand the code.
- masklinn 4y ago> The intermediary names are extremely relevant to the next poor sucker who has to understand what you were trying to do. There are lots of cases where they’re not and are just noise written in exactly the style of the original comment. Or worse all the one-shot temporaries get assigned to the same worthless name. Unless you live and die by the mantra that no expression can have more than one period, pipes pretty much just bring attribute/method chaining to arbitrary functions and expressions.
- substation13 4y ago> The intermediary names are extremely relevant to the next poor sucker who has to understand what you were trying to do. I just don't think that this is always true. Consider: const highestScore = players |> filter(x => x.isAlive) |> map(x => x.score) |> tryMax I don't see how this is better: const alivePlayers = filter(x => x.isAlive)(players); const scoresOfalivePlayers = map(x => x.score)(alivePlayers); const highestScore = tryMax(scoresOfalivePlayers); And you can add helpful comments to pipeline code if needed: const highestScore = players |> filter(x => x.isAlive) // Dead players cannot win |> map(x => x.score) |> tryMax More generally though, I don't see why forcing everyone to write out intermediary names all of the time leads to more readable code. If it's more readable to do so, I will. If a pipeline is more readable, why should we be prevented from using it?
- charles_f 4y ago> I don't see how this is better Case in point: > you can add helpful comments to pipeline code if needed The pipeline with explanatory variables explain to you what the steps are with code. Using pipeline you need to add comments to explain "what" you are doing.
- substation13 4y agoThen you can mix-and-match: const activePlayers = players |> filter(x => x.isAlive) const highestScore = activePlayers |> map(x => x.score) |> tryMax In any case, I don't see how being restricted to always using an intermediary variable for every step is an advantage.
- dalmo3 4y agoAnd then it's a pleasure to open the debugger and immediately see the values for each step.
- substation13 4y agoIf pipes are added to JS then IDE support will follow very swiftly.
- danwee 4y agoIt's only less noisy because of the simplistic nature of the example. In real world code, `one`, `two` and `three` and probably a chunk of code put together in a single line and it's difficult to find out what the hell they are doing. Concat together a few of these and that's a recipe for disaster. Less experienced engineers would add a comment at the top of the pipe chain explaining what's going on. More experienced engineers would divide and conquer and use temporary named variables (render commets useless).
- charles_f 4y agoAgreed. I'm not the one coming up with the one/two/three, I took it from the proposition. Explanatory variables help understand the steps of the process, this |> operator is receipe for unmaintainable, expedited code.
- dmak 4y agoThis is similar to Elixir. It's such a natural way to program and so useful. I wish all languages had this. Also shout out to bash pipes
- Name_Chawps 4y agoConstant remodeling is the sign of an ugly home.
- leishman 4y agoI love the pipe operator in Elixir!
- dmix 4y agoThe current JS one isn't as nice as the Elixir one, at least it wasn't when I tried using it via Babel a couple yrs ago.
- ollien 4y agoThe JS one does seem to have some more power than the Elixir one. For instance, in Elixir, if it's a bit kludgy to pipe to a second or third argument. I find this often when I want to insert into a map. You can always define more functions, but otherwise it's annoying, because you end up with something like computation() |> &(Map.put(my_map, key, &1)).() That said, with this less-power, you do kind of end up forced to design your functions in a way to where piping makes sense, and IMO it leads to cleaner and more consistent APIs.
- ch4s3 4y agoFrom what I understand the constraint is intentional to guide you in the direction of writing simple pipe chains.
- dmix 4y agoThat's probably a wise choice. Pipes are great for simple situations, mostly for code clarity. But it can be abused easily, like a hammer seeking nails. It's similar to await/async, you eventually start designing code that better suits that interface rather than pigeonholing it with complex syntax.
- spapas82 4y agoClojure has thread first ->, thread last -> and even thread as as->, to define where the pipe will be applied. I find it very cool!
- 4y ago
- nassimsoftware 4y agoTo give credit where it's due. I came across the proposal through this video : https://www.youtube.com/watch?v=h1FvtIJ6ecE https://www.youtube.com/watch?v=h1FvtIJ6ecE by Theo the CEO of Ping.gg.
- swyx 4y agoand kudos also belong to the people whove been heavily championing and bikeshedding the proposal on behalf of the rest of us for the last 8 years - which i just discovered is really nicely documented in the repo: https://github.com/tc39/proposal-pipeline-operator/blob/main/HISTORY.md https://github.com/tc39/proposal-pipeline-operator/blob/main... there's usually a ton of nuance behind the syntax considerations and i usually find that the people on tc39 care way more than i do about the things i never think about until its too late. peeking into their discussions is often very enlightening... and a reminder of how hard it is to do language design by committee and at scale.
- eknkc 4y agoI'm pretty sure this'd be a welcome addition to the language but to be honest, the syntax looks shit.
- zmxz 4y agoWhat syntax would you propose or use?
- galaxyLogic 4y agoI would propose -> instead of |>
- CodexArcana 4y agobut then is not a pipe!
- layer8 4y agoIt’s a horizontal pipe. It arguably actually makes more sense when the values flow horizontally from left to right through the pipe. In an alternative universe, Unix shell syntax may have used “—“ instead of “|” for piping.
- layer8 4y agoPS: The commands in Unix pipes are executed in parallel, and “|” could be interpreted as a mnemonic of that fact. However, the same is not true for the programming-language feature discussed here, which only processes singular values, not streams.
- vorotato 4y agohack pipes aren't "pipes" anyway. Every other language that uses |> uses it for functions, not expressions.
- kehrin 4y agoTo be honest |> looks more like a tipped over traffic cone to be begin with.
- icholy 4y agoPlease god no more...
- hajile 4y agoThey should have stuck with the F# proposal. The hack proposal just takes one more giant step toward turning JS into Perl. Hack proposal value |> foo(%) for unary function calls, value |> foo(1, %) for n-ary function calls, value |> %.foo() for method calls, value |> % + 1 for arithmetic, value |> [%, 0] for array literals, value |> {foo: %} for object literals, value |> `${%}` for template literals, value |> new Foo(%) for constructing objects, value |> await % for awaiting promises, value |> (yield %) for yielding generator values, value |> import(%) for calling function-like keywords, F# proposal value |> x=> x.foo() for method calls, value |> x=> x + 1 for arithmetic, value |> x=> [x, 0] for array literals, value |> x=> ({foo: x}) for object literals, value |> x=> `${x}` for template literals, value |> x=> new Foo(x) for constructing objects, value |> x=> import(x) for calling function-like keywords, F# proposal would make `await` and `yield` into special syntax cases or not allowed. I'd rather do await/yield the old fashioned way (or slightly complicate the already complex JS syntax rules) than add the weird extra syntax. Arrow functions are elegant and already well-known and well-understood.
- deleted 4y ago[deleted]
- xnorswap 4y agoIs either proposal compatible with later adding a backward pipe <| operator? If so I think that should be a consideration, even if in practice there isn't massive value in a backward pipe operator in javascript. It would be a shame to later want to add it and have to add another layer of kludge and messy syntax.
- ubertaco 4y agoYeah, F# has a backward pipe operator, and it works like this: a_value |> (fun a b -> a + b) <| b_value In F#, this works because of automatic function currying. Not sure how that would apply to Javascript, though. Without function currying, what would this even mean? a_value |> ((a, b) => a + b) <| b_value ...since there's no currying, you'd just immediately invoke the function that sits "in the middle" with `a` set to `a_value`, but `b` set to `undefined`. The Hack-style proposal though...I don't think it would work with a backward-pipe operator at all. Not without adding a _second_ special-case symbol, at least.
- CyberDildonics 4y agoI don't see how syntactic sugar makes any sense for javascript. Breaking compatibility is so severe because every browser needs to catch up, yet it doesn't actually enable anything that couldn't be done before.
- Nullabillity 4y agoNo compatibility is broken, old scripts will keep on working just fine. Scripts written for the new syntax won't work in old browsers, but that's the nature of evolving standards anyway.
- dmix 4y ago> Scripts written for the new syntax won't work in old browsers, And critically browsers are - for the most part - much more in sync these days across the board. A lot of web devs got burned through the years of IE6-IE11 and have a natural distaste for browser level changes. Pipes have been (formally) debated for 5yrs now by the JS people, so they aren't exactly being non-conservative about this one.
- mmis1000 4y agoUnfortunately. If your web page need to deal with Chinese Android phones, you still need to deal with old browsers. Their android system somehow always shipped with broken auto-update of webview or chrome. Which results in people open your webpage with quite old browser version.
- fnimick 4y agoIt lets a transpiler like Babel or Typescript use the new syntax and output the equivalent code that works on older browser. Same reason async/await was useful far before there was native browser support - you could immediately use it in your codebase and compile to a legacy browser target.
- CyberDildonics 4y ago
- heliodor 4y agoWhat a pain to type! The idea is great though.
- chadlavi 4y agoJavaScript isn't that kind of programming language. I don't know why people are constantly trying to make it something else.
- nerdponx 4y agoMy pet theory: Front-end devs are generally stuck with JS, but they wish they were able to use other languages, but they can't, and can't convince their managers to use Clojurescript or Purescript, so this is what happens. What's bonkers to me is that ECMA would prefer to keep adding features like this instead of adding a macro system.
- eddsh1994 4y agoJavaScript with macros? Please please no. Can you imagine that?
- yakshaving_jgt 4y agoI'm not buying that theory. I'd love if more front-end developers were eager to use PureScript or Elm, but popular sentiment appears to be that anything other than JavaScript is weird or hard or impractical or unreadable.
- deleted 4y ago[deleted]
- kibwen 4y agoJavascript is, without exaggeration (and with much chagrin), the language that has done more to popularize functional programming than any other programming language in history. It's only natural that they'd continue pushing on that front. :P
- arp242 4y agoIs this: Object.keys(envars) .map(envar => `${envar}=${envars[envar]}`) .join(' ') |> `$ ${%}` |> chalk.dim(%, 'node', args.join(' ')) |> console.log(%); Really better than: console.log(chalk.dim( `$ ${Object.keys(envars) .map(envar => `${envar}=${envars[envar]}`) .join(' ') }`, 'node', args.join(' ') )); That's the real-world example they have (I reformatted the second one slightly, because it looks better to me). Neither seems very good to me, and the |> version doesn't really seem "less bad". Can also write it as: process.stdout.write(chalk.dim( `$ ${Object.keys(envars) .map(e => `${envar}=${e[envar]}`) .join(' ') }`, )) console.log(chalk.dim('node', args.join(' '))) Which seems clearer than either because it splits out "print env variables" and "print out node args". And it would be even better with some sort of helper to convert an object to k=v string: console.log(chalk.dim(`$ ${dumpObj(envars)}`, 'node', args.join(' '))) --- I also feel this: > In the State of JS 2020 survey, the fourth top answer to “What do you feel is currently missing from JavaScript?” was a pipe operator. Is the wrong way to go about language design. Everyone wants something different, and if you just implement the "top 5 most requested features" you're going to end up with some frankenbeast of a language.
- pharmakom 4y agoThe Hack proposal is horrible imo because it doesn’t look like JS anymore. The F# proposal is 99% of the benefit whilst being actually approachable.
- galaxyLogic 4y agoI think we should have BOTH. Use |> for the Hack proposal and -> for F# -style.
- hajile 4y agoI'd far rather save that for an alternative switch expression with pattern matching. const slowSum = (lst: {x: number}[]) => switch(list) { [{x}, ...y] -> x + slowSum(y) //not tail recursive [{x}, {x: y}] -> x + y //alias second x to y [{x}] -> x //handle length 1 [] -> 0 //handle length 0 }
- rickreynoldssf 4y ago[flagged]
- progx 4y agoThat is a good idea, we need then more developers! More jobs for all! :-)
- adamrezich 4y agopreviously: https://news.ycombinator.com/item?id=28916775 https://news.ycombinator.com/item?id=28916775
- matlin 4y agoCan't wait for this. Pipes are awesome in Elixir and bringing them to JS/TS will be great. To me this is both concise and readable: const weather = `https://api.weather.gov/gridpoints/TOP/31,80/forecast` |> await fetch(%) |> await %.json() |> %.properties.periods[0]
- itslennysfault 4y agoUncaught TypeError: Cannot read properties of undefined (reading 'periods')
- giancarlostoro 4y agoCame here to say this. I am learning Elixir on and off, and I think pipes are amazing.
- qudat 4y agoIt’s pretty great in R too
- jsf01 4y agoWhile pipes are great in Elixir I think it’s important to look at the total cost to adding any new syntax in the context of the language that is considering them. In the JS ecosystem, idiomatic nested function calls are read from the inside out. And in most cases that is highly readable because a single layer of nesting is all you need. This proposal flips that on its head, so you have to retrain yourself to read code in a new way based on the presence of a |>. That adds significant cognitive overhead, especially in code that isn’t well written or that is already complex. Your example looks nice, but also just as nice is the .then() syntax you could have used. Now we’ll see code bases with both styles. You’ll have some team members who love pipes so much they’ll never call a function the old way doing x |> console.log and the rest of the team doing console.log(x). Sometimes, limitations are a good thing.
- pharmakom 4y agoThe pipe operator is awesome because you can use it to “extend” objects without messing with their prototypes. Missing String.titleCase ? Write your own! “hello world” |> titleCase
- teg4n_ 4y ago`titleCase(“hello world”)` , how you would already do this seems equally ok to me
- substation13 4y agoAssuming you are only calling one function - if there are more you get into a bit of a mess!
- renke1 4y agoThere was also a different proposal that allows objects to be extended: https://github.com/tc39/proposal-bind-operator https://github.com/tc39/proposal-bind-operator Personally, I don't use classes much, but sometimes I think free functions are a little too hard to find, so I tend to experiment with the following pattern. interface User { … } const User = { rename(user: User, newName: string): User { … }, getDisplayName(user: User): string { … } } const renke: User = { … } console.log(User.getDisplayName(renke)); Which makes finding an operation for a certain type easier to find (just write User and trigger autocomplete). The alternative is of course having renameUser (or userRename) and getUserDisplayName (or userGetDisplayname). The prefixed version would make autocomplete easier also.
- galaxyLogic 4y agoJava has https://www.eclipse.org/xtend/ https://www.eclipse.org/xtend/
- pharmakom 4y agoThis is the beauty of pipeline operator. It adds very little extra machinery - it’s just an infix operator that makes working with free functions easier.
- kriz9 4y agoTemporary variables are often tedious? I have found that well named temporary variables are the only clear way to comment code without actually writing the comment. The version with temporary variables is much easier to understand without having to read the rest of the code.
- vikingerik 4y agoThis. Temporary variables are the way to go for deconstructing a complex expression like this. Everything is more readable when you put the results of an expression with two to four terms in a well-named variable. Trying to put everything into one giant closed-form expression feels clever and smart, but it's really just getting in the way of the next poor sucker who needs to understand what you were doing. This works the way human cognition does, by batching. The way humans can fit more items in short-term working memory is to batch up related concepts into one item. This is how chess masters do it - they don't see a piece and look individually at each square it is attacking, they see the entire set of attacked squares as one item. This is why "correct horse battery staple" passwording works - the human doesn't remember twenty-eight individual characters, they remember four words. Temporary variables follow how human cognition works, particularly when the reader is going to be somebody else's cognition who didn't go through the process of writing it.
- itslennysfault 4y agoMaybe it's just that I'm just old and been doing JavaScript for 20+ years, but I HATE IT.
- evnp 4y agoconst isRandNumOdd = Math.round(Math.random() * 100) |> % % 2 |> Boolean; Excruciatingly contrived, but does this sort of arithmetic work in the Hack syntax? I'm genuinely curious, couldn't find any mention of modulo (or remainder) in the proposal.
- eddsh1994 4y ago% % 2 Gross
- ubertaco 4y ago...so only the first usage of `%` counts as a replacement? What if I need the value to be replaced multiple times, like this: |> `${%.id}: ${%.friendlyName} ${%.url}` I'm surprised they aren't going with an idiom like `$1`, `$2`, etc or something like in other languages that have "magic" lambda parameters.
- masklinn 4y ago> ...so only the first usage of `%` counts as a replacement? Hopefully not because there’s no reason to: in the same way you can use + or - as prefix or infix, % as value and % as binary operator are not ambiguous. % % % should not be an issue, though it’s useless and not exactly sexy looking. > I'm surprised they aren't going with an idiom like `$1`, `$2`, etc That makes no sense, $1, $2, and $3 are different parameters. Using your example, |> `${$3.id}: ${$1.friendlyName} ${$2.url}` makes absolutely no sense. Not to mention the very minor issue that $1 is already a valid JS identifier. > or something like in other languages that have "magic" lambda parameters. % is one of those, it’s what closure uses for its lambda shorthand. Scalia uses `_` and kotlin uses `it`. The latter two are ambiguous but I guess since pipes are new syntax there wouldn’t be a huge issue making them contextual keywords. Most language with “pipelines” are curried so it’s not a concern, and in the rest it tends to be a fixed-form insertion, so it’s quite inflexible, but in both cases APIs are designed so they play well with that limitation e.g. in curried language you’d have the “data” item last (that’s obviously in Haskell), which also allows for partial application in general, while in “macro” languages, well, it depends where you decide the magical argument should be inserted (IIRC in Elixir it’s the first, so functions written for pipe compatibility should take the main operation subject first). Clojure is cool because it has both plus a macro where you give it the name of the substituted symbol. However being a lisp the pipe macros are still prefix, not infix.
- dahart 4y ago> Deep nesting is hard to read […] Temporary variables are often tedious I’m a little torn because pipes might be pretty nice, especially for prototype code and small projects. One thing this proposal doesn’t acknowledge is that for production code, deep nesting’s often impractical and often considered an anti-pattern in the first place, so making it easier isn’t a common need or problem to have in my experience. Usually I’m going the other way, having to make it more tedious. Using temporary variables and breaking apart nesting is, far more often than not, necessary in order to do proper error checking, and just to make code readable, commentable, and refactorable, etc. I feel like what we need is not a way to make deep nesting easier to read, it’s a way to make temporary variables less tedious, perhaps while also piping from one function to the next… that would be really helpful in a deeper way than just adding another chaining syntax. Is the syntax is stuck at “|>”? I guess it’s not possible to override bitwise-or (“|”), but “|>” feels maybe a little clunky?
- gfodor 4y agoPipes are cool but this falls pretty hard on the side of adding too much cruft to the language.
- jsf01 4y agoThis strikes me as something better left to libraries. If you want to write in a functional style then Ramda, Lodash, Underscore, and plenty of others have pipe and compose functions. pipe(one, two, three) Easy to read. No new syntax. Extendable with arrow functions. Yes, there are some limitations in comparison to Hack Pipes. But those are far outweighed by not messing yet again with the language’s syntax.
- scotty79 4y agoFor me limitations are killing 90% of usecases. In absence of |> % syntax I'd nearly always would go with intermediate temporary variables instead of pipe(). "point-free" syntax feels horrible for me and wrapping everything in lambdas feels excessive.
- disantlor 4y agoI use Ramda pipe() all the time and use a descriptively-named intermediate/temp variables wherever I might want to write a comment
- Ross-Esmond 4y agoThis only applies to TypeScript, but error checking for this pattern from a library is much harder than error checking for native syntax. In order for TypeScript to check if the return value of one function has the correct type for the next function, you need to use generics, but generics don't allow for a variable number of generic parameters, so `pipe` would just have to use unknown or have dozens of overloads for each length of pipe usage (within reason). I know TypeScript is a different, optional language, and I see no reason why they couldn't add the feature without it being in JavaScript, but that's not generally how TypeScript operates. If JavaScript doesn't add it, TypeScript won't. There's also lots of tooling that will type check your JavaScript when available, which will benefit from a simpler model.
- runarberg 4y agoThe problem with that is that it is impossible to infer the type of the n-ary pipe() function. Libraries “solve” this by overloading that function with n annotations, but that really isn’t a permanent solution, especially for libraries with millions of users, as there will always be a user that puts n + 1 parameters in that function.
- bborud 4y agoYeah, sure, just heap in more stuff. Because it isn't chaotic and ugly enough yet.
- Someone1234 4y agoCan someone remind me again why there's never been movement to add a second modern language to web-browsers? JavaScript was created in a weekend and then stuff tacked on for the last 28 years. We know so much more about how to create programming languages today than we did then, and the whole "Year of the Linux Desktop" has become a WebAssembly meme now every year since its introduction six years ago, with it getting popular always next year, next version, with feature XYZ. Seemingly creating unmaintainable/debuggable mess from external languages with no true 1:1 into WebAssembly isn't as big of a hit as the originators expected. Yet every time someone asks why there hasn't been movement here it is "Year of WebAssembly is next year!!!" WebAssembly has managed to slow actual progression towards something good. With the browser monoculture you'd think it would be easier now than ever to start a fully integrated second language with WebAssembly compilation for backwards compatibility.
- capableweb 4y agoWebAssembly is just what you want, a second "modern" language that works in browsers. It hasn't reached 100% of its potential just yet, as there are some things missing for that (like DOM access) but once the language is feature complete, I'm sure most languages will have some sort of "Lang to WASM" tooling that'll allow you to write React apps in Ruby or whatever, if you so wish. Stuff like that takes time though, so if you're antsy, you have two options: get involved, or wait patiently.
- tracker1 4y agoThere's a few options out there if you don't mind being an early adopter. Most interactions via DOM bridging are slower than actual React... but it's kind of cool. Yew (Rust) is one that I've been following with interest, the hello world examples are interesting enough. I know people that have been liking the direction of Blazor (C#) more, but it has a larger initial payload, that I don't care for, it's also closer to SSR approach in the browser. The problem with both, IMO, is that they don't have a good UI library/toolkit. I find mui.com (with React) pretty much the best browser ui framework I've experienced (since 1996). To me, the first language + component library that targets WASM and that level of components will likely win. Flutter is probably the closest example I'm aware of, though afaik it's JS as the browser target, not WASM, but wouldn't be surprised if this changes assuming DOM interop for WASM becomes better performing.
- brundolf 4y agoThis proposal has been languishing for years and years. I'm not sure when it last made progress, but I remember waiting for it with anticipation around 2018
- asciimov 4y agoThis just complicates things if you have any kind of complex nesting. Something "simple" like: a(b(),c(),d(e(),f(g()))) Turns into the following: value |> b() |> a( %, c() , v2 |> e() |> d(%, v1 |> g() |> f(%) ))
- aabhay 4y agoNot to mention the confusion of nested percent vars
- scotty79 4y agoNope, it becomes: g() |> f(%) |> d(e(), %) |> a(b(),c(), %) Which makes super clear what processing is actually done. Which is the data to process and which are just parameters of processing. Because it could equivalently be: e() |> d(%,f(g())) |> a(b(),c(), %) If the data you process is rather produced by e() not by g(). This new syntax allows you to express intent beyond what's possible without it.
- _boffin_ 4y agoIf i saw that in a colleague's code, i'd be angry at them as that's not legible.
- scotty79 4y agoIt's no less legible than original code and at least expresses the intent of what's being processed, what are the processing steps and what are processing parameters. a(b(),c(),d(e(),f(g()))) is just function call soup.
- jibberjabb3r 4y agoThe proposed pipe operator could only be efficiently implemented via a transpiler pass to lower it to "regular" JS varaibles. I wouldn't want this feature in a JS engine due to the overhead it would require. Sometimes it's better just to say no to new features that yield questionable utility and don't reduce code size or complexity by much.
- captainmuon 4y agoI don't understand why you can't just use temporary variables. The article mentions mutation is bad, but what actually happens is that the name gets reassigned. No value is mutated. That brings me to something I really want in JS, actual unmutable values. If you use `const x = new SomeClass()`, you cannot reassign it, but you can change fields. The first time I encountered `const`, I thought it did the opposite. It would be cool if you could declare something (object, array) to be an immutable value. If you really want to introduce new operators, how about operator overloading? For example vector and matrix calculations become a lot clearer and less error-prone with infix operators. It should be technically easy to add them to typescript - rewrite the expression to a function call depending on the types of the operands - but the TS devs refuse to implement this on philosophical grounds unless it is implemented in JS. I guess in JS it would require runtime dispatch, but maybe that is not such a big penalty given that it usually uses a JIT anyway. Oh, and while we are at it, fix `with`. The JS with statement is ridiculous and deprecated anyway. It makes all fields of an object available in it's scope. Contrast with VB6's `with`, which requires you to use a leading dot and is much more readable: with (elem.style) { .fontFamily = 'Arial'; .color = 'red'; console.log(.borderWidth); // in actual JS this would just be // console.log(borderWidth); }
- brundolf 4y agoA problem is that you can only declare intermediate constants in a statement context, not an expression context. And with React, more and more JS devs are spending time in expression contexts Example: return ( <div> {foo(bar(stuff))} </div> ) There's no way to break out inline intermediate constants here; you have to bail out and do it up above the `return`. In this case that may not be too bad, but when you've got a hundred lines of JSX, things start getting really spread out
- shadowgovt 4y agoBut that's a symptom of issues with React, not issues with JavaScript. React's declarative model makes it easy to write unreadable spaghetti React declarations that are nested ten levels deep. Nobody should be adding features to JavaScript to encourage that. ("Please, I'm begging you, for the love of sanity... Refactor into more than one component. Just one time. Look, functional components even make that cheap and easy now. Please please please, just take some of that nesting and put it in a new component.")
- shadowgovt 4y agoThis makes me nervous. In general, I think adding features like this to a mature language is a misstep because it increases the cognitive load of "things you have to know to read other people's code." And that's strictly increases... Since changes like this can't remove previous approaches (for backwards compatibility reasons), we'll now have three syntaxes for function calls? Yuck. Left unchecked, this predilection eventually leaves you with languages like C++: a language where you can write good, safe code if you stick to modern methods, but good luck learning what "modern methods" are or finding tutorial books that don't teach you any of the bad-old approaches or, most importantly, working with other people's C++ code that still has `setjjmp` and `longjmp` in it because the language allows for it, therefore someone used it somewhere. (oblig: https://xkcd.com/927/ https://xkcd.com/927/)
- yamtaddle 4y agoI'm starting to think the entire direction of modern Javascript is one giant yak-shave built on enabling bad decisions. "I like functional programming so I'm going to do that in JavaScript" -> "Now I have a problem because JavaScript is not very good at that, so now let's radically alter JavaScript until it's... well, still not good at it, but it looks like it is, at a glance" (initially through libraries, now altering the language itself) "Let's make as many calls async by default as possible" -> "Oh but actually I need most of them to seem synchronous, even if they're actually async, like 90+% of the time" -> "Callbacks?" -> "Oh god that sucked... promises?" -> "Better but still not great, let's just... uh... add `async` and `await` and watch as their use becomes so hilariously common that it's now painfully obvious that the default behavior is wrong?"
- shadowgovt 4y ago> watch as their use becomes so hilariously common that it's now painfully obvious that the default behavior is wrong Hm. :) Hindsight being 20/20, perhaps "synchronous" was a bad default for a language embedded in an application space where the lifeblood is "network communications over unreliable channels." Still, seemed a good idea at the time(1) (1) ... at the time, they were doing a cute tech demo, there were alternative scripting languages under consideration, and I don't think anyone expected JavaScript to blow up to become the only viable option.
- dgb23 4y agoSaw a talk with Douglas Crockford[0] years ago. He said something like: Before JS classes got introduced he asked why they didn't just implement macros for the language. Classes are in fact just syntactic sugar. Just like async/await, and now this proposal. In hindsight he was right. JS would be better off if it did have macros. Much of the whole babel/webpack/react/ts stuff would be just a bunch of macros instead of idiosyncratic build tools and so on. And we would have had much less compatibility churn. In fact this proposal here, is trivial to implement with macros. Clojure has the same thing (threading operator) and it's just a macro. [0] https://en.wikipedia.org/wiki/Douglas_Crockford https://en.wikipedia.org/wiki/Douglas_Crockford
- gorjusborg 4y agoFirst, syntactic macros are great, and I've often wished for them to exist in javascript (and other languages). Second, I only trust macros to people who are disciplined to use them wisely. Third, I've met only a handful of developers I would consider disciplined in this way.
- dgb23 4y agoPeople who write widely used macros are typically of that last category.
- gorjusborg 4y agoI've been thinking about your response since you left it. I think that what you said is true in an ideal world. Sadly, there are many developers who want to believe they are the disciplined ones, when in fact they are the trouble makers trying to inflate their ego at their team's/company's expense. Along with discipline, there must be humility. Good luck finding that combination reliably.
- hajile 4y agoMozilla created SweetJS over a decade ago[0]. It added hygenic macros to JS and I'm sure everyone on the TC39 committee is familiar with it. There's a lot to like about it, but macros in such a complicated language as JS are hard to get right. They'd also potentially lead to huge fracturing in the JS ecosystem with different factions writing their own, incompatible macro-based languages. Look at JSX for an example. It's actually a subset of a real standard (E4X -- actually implemented in Firefox for a long time), but just one relatively small syntax addition has added complexity elsewhere. For example, `const foo = <T>(x:T) => x` is valid Typescript for a generic arrow function, but is an error if your file is using JSX. I like the idea of macros, but I suspect they made the right call here. [0] https://www.sweetjs.org/ https://www.sweetjs.org/
- AirMax98 4y agoThat % syntax is just completely unlike anything else I have seen in JS. As a multi paradigm language, JS typically suffers from whatever programming style is on trend when these features are implemented. We are apparently on the other side of the pendulum now, but I can’t remember the last time I worked with a class and felt like that was right either.
- tgv 4y ago> JS typically suffers from whatever programming style is on trend Perhaps they are just jealous of the C++ committee. How many lambdas have they proposed and added to the language? At least 3: the C one, the Java one and now the Rust one.
- TheRealPomax 4y agoWe said the same things when arrow functions got introduced using an outlandishly un-javascripty syntax, as well as when templating strings got introduced using symbols that were the domain of LaTeX. Now they're "just what JS looks like" to both old and new JS devs. Look past the syntax, because you'll master it quickly enough and 2 years down the line forget it was ever not part of the language: does the actual functionality it introduces improve on what we can do and how we write and understand code or not?
- tracker1 4y agoWorking with something that uses an ES3-like level of the JS language, it's actually painful not having some of the conveniences added in the past decade+.
- TheRealPomax 4y agoI see someone's using Photoshop.
- zackmorris 4y agoCool stuff, but I miss the "it" variable from HyperTalk (the language used by HyperCard) which contained the result of prompts to the user. Just search for "The it variable": http://www.jaedworks.com/hypercard/HT-Masters/scripting.html http://www.jaedworks.com/hypercard/HT-Masters/scripting.html ask "How many minutes do you want to play?" put it * 60 into timeToPlay -- convert it into seconds Today we could have a reserved keyword that holds the result of the last statement executed. A practical example adapted from the article using "it" might look like: Object.keys(envars) it.map(envar => `${envar}=${envars[envar]}`) it.join(' ') `$ ${it}` chalk.dim(it, 'node', args.join(' ')) console.log(it); A better name for "it" today might be "_", "result" or perhaps '$' in a shell-inspired language like php. Most shells support "$?" so that could work too: # prints 0 true ; echo $? # prints 1 false ; echo $?
- __ryan__ 4y agoAn alternative is to make the pipe operator a simple function application and provide syntax for creating simple pipeline functions. For example: left |> right Would semantically translate to: right(left) And you could define a pipeline function like so, where the following: const myPipeline = @[ one(@), @.two(), @ + three, `${@} four` ] Would translate to: const myPipeline = (value) => { const _1 = one(value); const _2 = _1.two(); const _3 = _2 + three; const _4 = `${_3} four`; return _4 } Or: const myPipeline = (value) => `${one(value).two() + three} four`; And you could define the placeholder value name (which would allow nesting): const myPipeline = @it [ one(@it), @it.two(), @it + three, `${@it} four`, ] You'd combine the two syntaxes to get immediately-invoked pipeline functions: // Using a modified example from the proposal: envars |> @ [ Object.keys(@), @.map(envar => `${envar}=${envars[envar]}`), @.join(' '), `$ ${@}`, chalk.dim(@, 'node', args.join(' ')), console.log(@), ] This is better, in my opinion, than building the '%' placeholder syntax into the pipe operator.
- pier25 4y agoThis is cool but it always frustrates me to see these the TC39 focusing on these little improvements to the language instead of taking big bold steps that would have a much more significant impact. Stuff like types, data binding, reactivity, etc. These would save so many kbs and CPU cycles if implemented natively. The world sorely needs that. God knows how much energy is wasted in sending and processing huge bundles of JS billions of times every day.
- masswerk 4y agoI don't think that it's a good idea to introduce a left-to-right flow into a language, which strictly assigns righthand values by general design. The text mentions a back-and-forth in reading, this introduces a back-and-forth intellectually. (There's already a limited left to right capability, namely by chaining expressions using logical operators, especially when used outside of condition. It should be mentioned, however, that this is already confusing to some.)
- masswerk 4y ago!(funcA(transferObject) || true) || !(funcB(transferObject) || true) ... // ceci n'est pas une pipe function pipe(func, obj) { return !(func(obj) || true); } pipe(funcA, transferObject) || pipe(funcB, transferObject) ... // cecie n'est pas une pipe non plus function pipeB(func, obj) { func(obj); return 0 | 0; } pipeB(funcA, transferObject) | pipeB(funcB, transferObject) ... // no more pipes, please! funcA(transferObject), funcB(transferObject) ... // finally... ;-)
- no_wizard 4y agoI hope Records & Tuples[0] land before this does. It would have meaningful and far reaching positive effects for the language, without much controversy. Like most of these things, it takes about 5-7 years for it to permeate through enough of the engines to be meaningfully useful in the day to day of web developers (node / deno typically 12-18 months tops). It would drastically speed up existing code once wide adoption is gained though. I don't think the Pipe Operator would be as useful in comparison. I really hate how long the Records & Tuple proposal has been languishing at stage 2. It could've shipped years ago if not for a syntax debate that dragged on and on without being fruitful[1] EDIT: there is a class based version of this, that is stage one, for adding structs[2]. They behave similarly. [0]: https://github.com/tc39/proposal-record-tuple https://github.com/tc39/proposal-record-tuple [1]: https://github.com/tc39/proposal-record-tuple/issues/10 https://github.com/tc39/proposal-record-tuple/issues/10 [2]: https://github.com/tc39/proposal-structs https://github.com/tc39/proposal-structs
- hajile 4y agoI've been wishing for this for years. It opens up whole new ways of doing stuff when tuples and records are just primitives. They never mutate, so representing them efficiently right from the start is possible. They are primitives, so passing should be easier to do. Concurrency becomes a lot easier to add to the language because if you limit it to primitives (which are all immutable), you get a lot of safety guarantees. I'd love to see either channels or actors baked into the language in the future. Unfortunately, I don't think the pipe operator is the issue. Both of the proposals are just syntax. Implementing either just consists of a very straight-forward translation into already-existing syntax then running through the rest of the JIT as normal. The real issue is stuff like the private variables in classes garbage. It added unnecessary complexity to the syntax (and is very ugly and perl-like). I've never met a senior JS dev (outside of TC39) who actually wanted or used it. Despite this, the JIT engineers couldn't wait to refactor all kinds of stuff all across the JIT to make this happen. The time spent implementing private variables SHOULD have been spent implementing the infinitely more useful records and tuples.
- no_wizard 4y ago
- 8K832d7tNmiQ 4y agoYeah, no. The idea of even reversing function order just to use the piping operator itself is already bad enough to even consider using it.
- vorotato 4y agoThese hack pipes are a trojan horse. People wanted elixir/F#/ocaml aka function pipes, and what we got was unreadable line noise. I argued against it until I was blue in the face, decided it was bad for my general wellness to keep it up. I genuinely would prefer no pipes over this. I couldn't find a single example where I preferred it. The token they chose already has a meaning in Javascript! The arrogance and willful disregard for readable code was astonishing. The only tangible reason I could pull as to why they picked the least popular implementation despite all the outrage was "someone at google didn't like the function pipes". Even if you think we should avoid it because some google employee doesn't want it, that doesn't mean you should ram in an even worse implementation. I had to block the TC39 discussion because I was just going to get argumentative because they weren't listening at all, and they were dismissing actual concerns without any explanation.
- xiphias2 4y agoCan you give a link to the discussion? It would be quite relevant here.
- noiv 4y agoI had a hard time rethinking decades of frontend projects where pipes would simplify code at least two times. However, in the backend please go ahead. Having said that, can anybody provide an example with error handling per pipe? You know servers are bitches :)
- vlunkr 4y agoI love the pipe operator in Elixir, but I've never really wanted it in JS. It's critical in Elixir because the design of the runtime (immutability, functions only, no methods). In the rare cases that you might need it, like their three(two(one(value))) example, function composition is available from libraries or easy to do yourself. It's just my opinion, but I think turning JS into a kitchen sink of language features is a mistake.
- jonnycomputer 4y agoAnyone doing data analysis knows the value of a pipe.
- mihaic 4y agoI know I'm in the minority, but I dislike both version of the syntax, since I think chained code is almost always worse than just being forced to use intermediary variables for everything. While it's fine to say Object.entries().map(), anything more is not only harder to read, but makes debugging and maintenance more difficult.
- agumonkey 4y agoI played with coconut recently (fp layer on top of python) and the `|>` syntax felt very annoying compared to `.` (even though it felt ok in Ocaml..) But in an OO underlying language it's probably impossible to reuse it.
- snow_mac 4y agoI *HATE* pipes. For example from Elixir School (https://elixirschool.com/en/lessons/basics/pipe_operator https://elixirschool.com/en/lessons/basics/pipe_operator): ``` foo(bar(baz(new_function(other_function())))) ``` They offer this example of an improvement: ``` other_function() |> new_function() |> baz() |> bar() |> foo() ``` While yes, pipes improve readability, how do they deal with errors? How do they deal with understanding what each thing is supposed to return? I would prefer something like this, (descriptive variable names): ``` var userData = other_function(); var userDetails = new_function(userData); var userComments = baz(userDetails); var userPosts = bar(userComments); var finalUserDetails = foo(userPosts); return finalUserDetails; ``` Then I can easily debug each step, I can easily understand what each call is supposed to do, if I'm using type script, I can assign types to each variable. I strongly oppose clean code for the sake of looking pretty, or being quick to type. Code is meant to be run and read more then written, it should be descriptive, it should describe what it's doing not a nasty chain of gibberish. Hence why most people hate REGEX.
- jameshart 4y agoI think there is merit to the argument that if naming is one of the hard problems, programmers writing that code are having to do a lot of ‘naming’ and that is hard for them. The proposed pipe operation eliminates those names and lets the programmer just use %. But these variables are rarely the kind of thing it’s hard to name, so it feels like a slightly disingenuous argument.
- 8note 4y agoNaming is hard because names are useful. Getting rid of the name moves the hard problem, rather than solve it
- kbp 4y agoUsually that effort should go toward naming functions rather than their results, though, and if the functions have good names, the results don't need them. In this example, `other_function` could have been named `get_user_data`, `new_function` could have been called `extract_user_details`, whatever. Once you have good function names, which you should generally be spending a lot more effort on than good local variable names, you won't find any value in adding variables like `var foo = get_foo()`.
- born-jre 4y agoLETS FREEzE JS, why are we adding more stuff to js ? (੭ ˊ^ˋ)੭
- jansommer 4y agoCertainly looking forward to the pipe operator - if it ever lands! It doesn't seem like it's moved to the next stage recently in the commit history, and it's been discussed for ages now.
- mg 4y agoI wonder if "%" placeholder is the right approach. It makes the code longer in most cases. Without pipes: a = d(c(b)) With pipes in the proposed form: a = b|>c(%)|>d(%) My first approach to design it would be: a = b~>c~>d So the rule is that on the right side of the pipe operator (~>) there is always a function. We don't need parenthesis to indicate that. If the function takes more than one argument, it can be defined by another value on the left of it. Without pipes: a = d(c(b,7)) With pipes in the proposed form: a = b|>c(%,7)|>d(%) With the ~> approach: a = b,7~>c~>d
- tracker1 4y agoYour ~> operator is effectively the F# style pipelines (using |>) that have already been rejected twice... Personally, I was fine with F# style myself... Hack style in TFA is also fine, not sure on `%` specifically though. In either case, I've lost hope of seeing either pipelines or decorators actually make it through committee in my lifetime at this point... it's been about a decade now.
- mg 4y agoI haven't looked into F# pipelines for a while, because they seemed so bloated to me that it hurts. Isn't it that this: a = b|>c(%,7)|>d(%) Becomes something like this in F# pipes? a = b|>(b)=>c(b,7)|>d If not, what does it become? My suggestion is much shorter: a = b,7~>c~>d
- zelphirkalt 4y agoFor languages, which do not have built-in the power to change themselves, in many cases it might be better to stick to their feature set, instead of introducing even more language concepts. Look at how much work is involved to get something as simple as pipelines. As if they will ever find the right syntax for everyone. If we used a normal function we might have to include a library or a dependency or heck, just take 5 minutes and write a pipeline function oneself. Sure, it will not be syntactically as minimal as a _change of the language itself_ to allow pipelining, but at least it will not introduce even more language features and everyone can easily look at the definition change it or include it in their own projects. Ideally we would strive for a minimalistic set of features, which in turn can implement anything we want. Adding more and more features, without introducing a way for the user to modify the language without a commitee and a lengthy process, seems short-sighted. If you want to give the user more power over syntax, introduce a well engineered macro mechanism (plenty of examples in other languages) and let people develop standards and libraries as macros and libraries. Later on decide what to take into the standard language. Similar to how jQuery influenced modern JS standard functions like querySelectorAll. Even if you don't take something into the standard language, users are still free to include it in their project. No harm done and everyone gets their lunch and can scratch their itches.
- epolanski 4y agoI understand the hassle around pipelines. Entire ecosystems like `fp-ts` or `effect-ts` are regularly used with pipeable APIs and no one beats an eye. The only thing that was needed was a syntax that would've allowed this: pipe( "foo ", capitalize, // "FOO " trim, "FOO" shout, "FOO!" console.log, undefined ) to not require `pipe`. "foo " |> capitalize |> trim |> shout |> console.log Why `pipe`? Because otherwise you need to write it like that: `console.log(shout(trim(capitalize("foo"))))` which is unnatural as you need to read the chain in reverse order and is especially hard in less trivial cases than a sequential string manipulation.
- ravenstine 4y agoAs someone who actually loves JavaScript, I think this is a really bad idea. Maybe it has a place in other languages. I really don't want to see it in JS. We don't need more ways to do things implicitly or bass-ackwards from how they're actually behaving underneath. Syntactic sugar rarely makes code more readable. This operator only makes code seem more concise and as if it's executing in a way that it actually isn't. I can absolutely see junior developers running wild with this kind of thing. JS should be kept simple. This operator is not simple. It now makes understanding expressions more complicated. JS has its quirks and rough edges, but its power is in its simplicity. Please do not import the mistakes of other languages into JavaScript. If someone wants this operator, they should be forced to use a Babel transform or to compile their own JS interpreter. OR just compile your favorite language runtime with the pipe operator to WASM.
- de_keyboard 4y agoAt work I am always hopping between F# and JavaScript and I'm convinced that if more people had this experience they would want the pipe operator in JavaScript. Unfortunately it's hard to convey the advantages to those who haven't used it. Maybe it's because it's such a simple bit of syntatic-sugar?
- tambourine_man 4y agoJavaScript continues on its eternal and unavoidable march from a small quirky language towards a huge quirky language.
- 0xmarcin 4y agoSeems to me that someone "stole" it from F#. F# probably also take this idea from some unknown (to me) language. Here a similar idea in C++ https://www.fluentcpp.com/2019/10/18/piping-to-and-from-a-stream/ https://www.fluentcpp.com/2019/10/18/piping-to-and-from-a-st... This is generally a weakness of text based programming languages, that you cannot easily express graph flows like: / B1 \ A>-| | -> C \ B2 / |> operator only solves the problem for non-branched flows like A -> B -> C making them more readable by removing nested calls. In theory you can create something similar using JS OOP by attaching e.g. map/use(func) methods to every prototype: function use(func) { func(this); return this; } function map(func) { return func(this); } and then: (1).map(n => n+1) .use(console.log) .map(n => "n = " + n) .use(n => console.log("str = $n")); Introducing a new operator instead of a library is a huge effort. I don't think this proposal will succeed, especially that current custom operator support in JS is nil.
- 6510 4y agothree(two(one(value))) . value |> one() |> two() |> three() . a=one(value); a=two(a); three(a); . pipe(value, one, two, three) all seem fine but nr 2, where does the return value from three() end up?
- grumpyprole 4y agoThe "Hack" pipe really is a bit of a hack. It conflates two orthogonal issues, a consise lambda syntax and function application.
- jrockway 4y ago> In the State of JS 2020 survey, the fourth top answer to “What do you feel is currently missing from JavaScript?” was a pipe operator. In 2021 (https://2021.stateofjs.com/en-US/opinions/ https://2021.stateofjs.com/en-US/opinions/) it fell to 7th place.
- nmnvzr 4y agoIt looks super ugly.
- thot_experiment 4y agoThis language is a garbage pile, a literal scrap heap. Look over there, that's the kitchen sink. For fucks sake we added the absolute aesthetic disaster that is CLASSES to the thing. The strength of this language is that we throw in everything and the kitchen sink. I've had one wish for the last 5 years and it's for this god damn operator to land in the language. Please just throw it in there like everything else, it will make my life so much more pleasant. If you don't like it you can just ignore it studiously just like I do with classes.
- ht85 4y agoI'll only allow it if you can pipe `|> debugger`
- erixM 4y agoWhat if you have to use the remainder operator? How would "|> % % 2" work?
- spiralx 4y agoThe same way any other infix operator would, the operator has to have an expression on either side so the first % can't be an operator, and you can't have two expressions next to each other anyway i.e. you can't have "x x" or "f(x) x" or "% %"., so the second % has to be the % operator. Definitely looks strange and could be confusing at first, but the syntax is unambiguous to parse. Add a comment, write "|> (%) % 2" perhaps, or just create a mod function and write "|> mod(%, 2)".
- MathMonkeyMan 4y agoCan't javascript already do [this][1]? const thrush = (value, func, ...funcs) => func ? thrush(func(value), ...funcs) : value; // no new syntax required thrush( envars, Object.entries, x => x.map(([key, val]) => `${key}=${val}`), x => x.join(' '), x => chalk.dim('$ ' + x, 'node', args.join(' ')), console.log); [1]: http://www.davidgoffredo.com/thrush.html http://www.davidgoffredo.com/thrush.html
- runald 4y agoThrush combinator is neat. Here's another without using recursion: const thrush2 = (value, ...funcs) => funcs.reduce((value, func) => func(value), value); const thrush3 = (value, ...funcs) => { for (const func of funcs) value = func(value); return value }
- MathMonkeyMan 4y agoReduce is perfect for this, nice point.
- 01acheru 4y agoThe syntax is simply disgusting, some examples look worse with pipes than without them. I understand it is stage 2 now but can we do something about it? I'm seeing that most comments here and in other places are critical, why should we get this stuff in JS because of a vocal minority that is trying to push it on everyone else? If we keep adding the fifth thing asked on state of JS year after year we will end with some frankenstein weirdness... JS was getting better and better, let's not make it worse.
- trekkie1024 4y agoI actually find the original easier to read than the piped example given for this one: Original jQuery.merge( this, jQuery.parseHTML( match[ 1 ], context && context.nodeType ? context.ownerDocument || context : document, true ) ); ----------------------------------------- With pipes context |> (% && %.nodeType ? %.ownerDocument || % : document) |> jQuery.parseHTML(match[1], %, true) |> jQuery.merge(%); I think F# pipes are ideal in more complex cases, the % can add unnecessary complexity when reading code. Alas, it looks like we're not going to get that.
- e-dant 4y agoFunctions are universal. Choose them over everything else. Functional F# syntax or bust.
- joshxyz 4y agoThis feels like code golfing. To messy to explain to newbies. Just extra friction. You have to explain how the syntax works, the environment it works in, and the error handling. What the hell.
- pixel_tracing 4y agoWell here’s a solution to a problem that didn’t really need any solving. How about spending time on: • Actors • Real Immutable Structs ? This is going to create more code unreadable, while it may be cool and clever to put a pipe in and call it a day, I imagine most production code will be a pile of nested pipe gibberish that any junior engineer or new hire would pull their hair at trying to piece together. We want to create features, and solve business problems and that in turn also requires maintenance. This is just some clever macro wrapper around currying. While functional style is cool and all I don’t really see much value for most day to day workflows