I guess I'll just throw myself to the wolves here by saying that I'm generally in favor of a pipe operator, I believe avoiding unnecessary temporary variables is a very valuable thing for the reasonability of code, and that while I'd prefer F# style pipes plus a clean partial application syntax/approach, I can certainly live with the Hack style pipes and agree that F# would be very unidiomatic in the absence of better partial application.
Furthermore, I'm continually astounded by the naysayers who are opposed to language evolution as a general principle. I read these sort of objections all the time and all I can think is that if we took the "change almost nothing about our tools - think of the juniors!" approach advocated by these folks, we'd still be writing C and the industry would be an order of magnitude smaller than it is. Software development is a unique field of endeavor where it is remarkably easy to build and then use new tools, and while it does lead to lots of stressful change, it also leads to our discipline accomplishing more and more as the years pass. Thankfully, the silent majority seems to understand this just fine, which is why despite the tenor of the conversation around these (and other) parts, our tools do continue to get better and better as the years go by.
> I believe avoiding unnecessary temporary variables in a very valuable thing for the reasonability of code
I’m not convinced temp vars are avoidable in production code, since usually you end up needing error checking/handling on intermediate results anyway. Teams I’ve worked on generally try to avoid dense nested and/or chained expressions for this reason, but also because too much density or conciseness also limits the readability/reasonability of code.
I like that pipes turn nested expressions right-side out like the article points out, but temp vars already do this too.
The other thing that temp vars help with is making it easier to change functions in the middle of a sequence. If you have a nested expression, sometimes having to refactor something in the middle leads to a bigger avalanche of changes than necessary. Using temp vars might be somewhat less churn in some cases due to already haven broken down the expression into pieces. Maybe this pipes design will allow that too, it’s hard to say without using it for a while.
In practice pipes actually work in the opposite direction, the one you like: they allow people to break down nested expressions. Pipes allow the language to report granular line numbers and you can do error handling in them, if it’s functional (not if you need try catch of course).
While I’ve been on the team pushing for pipes and for the Hack variant, I do appreciate that there are many instances where giving an expression a name is helpful.
This is a tool that comes with great responsibility. My hope is that people will learn how to use it. It’s worth it imho the added language complexity.
The more ways there are to write in a language, the harder it gets to read the code of someone else using a different style guide, or with different preferences, than you. This matters a whole lot in a language so completely dependent on 3rd party libraries as JS is.
Not that I think this will hurt much, as the language and community fled that barn long ago. There are already 50 ways to do most things in JS, half of which will be present in your deps, and only 5 of which a typical active JS developer will happen to have stored in their brain at any given time.
This is certainly true. But this is also true of human languages, very few of which 'stop development' at the level of complexity of even the most complex programming language.
I love a good lisp or ML, and all things being equal, less syntax is probably better than more. But flexibility is also a good thing, and the irony is that the proof is almost always in the pudding - if a language gets too complex (e.g. Perl, C++), it is almost always true that the community eventually establishes solid guidelines around which idioms to avoid (or when to avoid them). Sometimes there's pain, but rarely do languages die because of it.
I think it's possible to have a reasonable debate about when to introduce new language features, but around here, in my opinion, the pendulum has swung very far toward the "don't move my cheese" side of things.
Programming languages are tools. I don't like it when I find out I'm going to need yet another set of bits for some new type of screw head, either. Doubly so if it's at-best marginally better than the two next-best kinds I already own. Takes up space, more shit to misplace or mistake for something else, et c.
What do you want from this community? All we hear, day and night, is how JavaScript sucks and has gotten ridiculously complicated. Now someone shows up with a proposal for pipes or the F# people and their pipes, like, what do you want from us?
We’re aware of what’s happening, or has happened to JS, and we’re having the realistic discussion about this stuff finally. The once ‘oh neat, new JS thing, let’s use it’ phase is over. We’ve seen the concrete impact of too much shit layered on top of each other in actual workplace codebases.
If you wanted this, you shoulda came earlier, the party is finished. We went too far between 2010-2020 and we’re all exhausted. It’s got nothing to do with ‘don’t move my cheese’ and everything to do with ‘I think we’re good over here, thx’.
We need to take a step back, evaluate all the nonsense (clean up after the party), before we can throw another one.
Like, it’s all good F# pipe people, totally appreciate your input, but we have our own clusterfuck that needs to be sorted out at the moment, maybe next time.
> This is certainly true. But this is also true of human languages
Yes, but this goes to the parent's point. Natural languages dialects can be so distinct that even though two interlocutors may be speaking the same language (e.g. English), they still might not be able to understand one another.
This happens in computer programming languages as well. If you're a C++ programmer who uses smart pointers for everything, then you will have a rough time with my C++ code that uses raw pointers for everything. Even though we would both call ourselves C++ developers, our code follows different idioms, they are essentially different "dialects" of C++. I put dialect in quotes, because I wonder if there is any effort to track, catalogue, or otherwise create a taxonomy of computer programming language dialects as is done for natural language dialects.
Like, you can figure out where someone was born based on the words they use. Can you figure out what kind of projects a person has worked on, the companies they worked for, or the school they went to based on the style of code they write? This would be the job of a linguist or sociologist I think, and I don't know how often they look toward or communities for study.
> The more ways there are to write in a language, the harder it gets to read the code of someone else using a different style guide, or with different preferences, than you. This matters a whole lot in a language so completely dependent on 3rd party libraries as JS is.
Hmmm, kinda.
The pipe operator enables more readable code, so whilst I agree there would be more ways to do things, the average code readability could well go up.
it also leads to our discipline accomplishing more and more as the years pass
All that seems to be being accomplished -- especially in the JS world -- is reinventing the same things over and over again in some sad attempt at "modernity", but using increasingly more resources to do so (and in the process, irritating more users and even developers.)
and the industry would be an order of magnitude smaller than it is
It's already too big, having traded quality for quantity far too much. I get the feeling that a lot of developers seem to be stuck in some weird "bubble of positivity" that makes them immune to negative feedback and think they can do whatever they want and users will love them for it, but if you talk to the users, the reality is quite different.
As the old saying goes: "It's not the tool, it's how you use it." If we stop trying to constantly create and promote new tools, maybe developers can start actually getting good at using the existing ones, and only then will quality go up.
> I read these sort of objections all the time and all I can think is that if we took the "change almost nothing about our tools - think of the juniors!" approach advocated by these folks
Weird, when I started programming I wished there were a pipe operator and I never understood why it didn’t exist in most languages.
> I read these sort of objections all the time and all I can think is that if we took the "change almost nothing about our tools - think of the juniors!" approach advocated by these folks, we'd still be writing C and the industry would be an order of magnitude smaller than it is.
I don't think that's what those people mean. What those people mean is "if you want something else than JS, use something else than JS". If you want pipes, you can already use OCaml, ReScript, F#. They compile to JS and work well.
> Thankfully, the silent majority seems to understand this just fine, which is why despite the tenor of the conversation around these (and other) parts, our tools do continue to get better and better as the years go by.
Which is also why brain power is lost of things like this or the walrus operator in Python. JS is a fine compilation target. If you really like |> and JS, you can use a babel plugin! I don't see the point of adding it to the base language.
Maybe if people stopped always using the most popular option available, we wouldn't need to constantly add stuff to this popular option, and could have a rich and diverse ecosystem.
I think this just fundamentally misunderstands how languages work in practice. In the vast majority of cases, switching languages is impossible, whereas picking up new words or idioms within the same language is very practical.
And as far as I know, this will be in a future version of ECMAscript, which means that most people will in fact be using this via babel?
"Switching languages is impossible" is a self-fulfilling prophecy. You think you can't, so more stuff get added to the language, so it's less justified to switch, etc. And then you end up with people trying to use immutable structures in JS and having way worse performance (but there's records and tuples coming!). Many people that said that switching languages was impossible happily migrated to Typescript, because the migration was easy. You could do the same with something like ReScript, but instead of getting the codebase more and more strict like with TS, you would transform it more and more into ReScript. There's also often lots of opportunities for small greenfield projects/part of projects.
> And as far as I know, this will be in a future version of ECMAscript
This may be in a future version of ECMAscript. Stage 4 proposals are the one accepted, stage 3 are almost official. This one is stage 2.
It's not _just_ a self-fulfilling prophecy - there are other factors at play here beyond simply "X is good enough". But sure, the more that dominant languages try to pick up good features from other, better languages, the more they will tend to maintain their dominance. This is the way nearly all successful systems behave, incidentally. Please note that it's going to be tough sledding trying to convince JS users not to try to improve their language so that some other clearly superior language can come along and take its market/mindshare.
And, well, yes, I realize we're talking about a future maybe-feature. That said, I assume you're granting my counter-point about how, no matter how you slice it, this is still ultimately "just a babel plugin"?
> Please note that it's going to be tough sledding trying to convince JS users not to try to improve their language so that some other clearly superior language can come along and take its market/mindshare.
My point is that there's no "clearly superior language" compare to JS. Some people want a ML flavor, some people want a C# flavor, some people want a Lisp flavor. I wish people would just use those language and improve the interops instead of putting everything into JS.
Anyone else concerned that this whole thing is being justified by this survey[1], in which pipes were "the fourth most requested feature". They neglect to mention that the fifth most requested feature (<1% fewer votes than pipes) was "functions", making it pretty clear that we're probing the "people who have no clue what they're talking about" sector and we probably shouldn't be adding any new syntax to the language based on their input.
Furthermore only 7% of respondents answered that question at all, so the real takeaway is: 0.7% of survey said "add pipes" and 93% said "please don't add anything it's fine as is just leave it alone we don't need to be the next Python". This isn't convincing data in the slightest.
As for their actual example... why change it at all? I think any reasonably adept JS programmer can look at their representative "console.log" example and immediately tell whats going on. Heck, it's using the same semantics as every other C language, so really basically any programmer can look at it and immediately tell whats going on. Contrast to their "proposition" where suddenly everyone in the world (minus the Hack folks I guess) needs to take time to learn what the new weird looking symbols mean.
I could not disagree more. Temporary variables as you perform transformations are great because they force you to verbalize what it is that you're actually doing. Descriptive naming is hard, but in contexts like this it's an actual godsend for anyone who isn't the person writing the initial code. I think the use cases are not convincing, I would much rather this be a Babel plugin than a core language feature.
This is a contrived example in terms of the actual functions being called, but it's not out of the question to have at least this many transforms (just think of a method chain of more than two or three operations if you are coming from an OO point of view).
My main reason for replying to this thread is to counter this idea that pipes are inelegant—this simply isn't true. At the same time, I personally don't care if they make it into JS or not.
this is the age-old "do it the way I'm used to and it'll be better" argument.
Descriptive variable naming _is_ hard, and unlike function and parameter naming, it is avoidable in many cases! By naming a temporary variable, you ask other developers to consider the possibility that you might be doing multiple things with it - not doing so allows a simplified read with confidence that this is a pure function pipeline with no unrelated side effects.
Naming is a great and hard and valuable thing - temporary variables are only good inasmuch as your readers habitually expect them - and habits can be changed, thank goodness!
Sometimes things just don't have a good name. Focusing on a few well-named temporary variables that corresponds to logical steps usually yield clearer code than trying to name everything.
There's truth to that, but I also find that if I can't name something chances are that what I'm writing isn't as sensibly structured as it could be. I would rather solve these problems by creating new scopes than to effectively strip away names in favor of a chain of generic function calls.
That's true, in these cases I usually write a function that's basically a composition of functions. The power of the pipeline operator is ad-hoc function composition. This is useful if you have lots of very generic functions, and don't want to name every single composition. There is already the f(g(h(x))) syntax, of course, but it's not the most readable.
I do agree in general that this operator isn't really needed. Probably not worse than Python's := or structural pattern matching, it's one of those things where either you base the language on it, or you don't and adding it is not really necessary.
> Temporary variables as you perform transformations are great because they force you to verbalize what it is that you're actually doing.
This is incredibly important because so many developers don't use ways to describe the intent of what they are writing at all. It's bad enough many believe the swill that "the code is the documentation", but to then add new ways to make code less self descriptive is... worrying.
I hate that following assignment in the examples requires knowing precedence for the operator vs. the assignment operator. The = sign visually "points at" the first function call, but it's operating on the return value of the last function call, to which it has no spatial or visual connection.
This will be a great thing for Javascript. One thing I noticed, however, was that the main arguments for Hack-style pipes over F# pipes really highlight the difference between functional languages like Haskell and languages like JS: in Haskell one designs functions for composition (and more generally, one designs structures for combination), whereas the fact that F# pipes cause awkward contortions in JS is mostly related to the fact that functions aren't designed for composition.
While I marginally prefer F# pipes, both will drastically improve the readability of JS code.
The Hack-style pipes are immediately useful even when you introduce steps that are not unary functions.
With a lot of stdlib using functions / methods of higher arity, I suppose the Hack version will lead to shorter and easier-to-read pipes in most real codebases.
I understand why the Hack-style pipes are probably a better choice for JS as we find it and its ecosystem, it's just that, having switched from my main languages being JS/TS and Python to now using Haskell most of the time, this contortion was what really struck me -- the emphasis on currying and function composition in pure functional languages just makes this a lot more natural.
As I said above, however, I think either method will be a fantastic addition to JS.
While many functions have an argument that is clearly "primary", some just don't. I personally find placeholder to be a better option, especially if used with named arguments.
Seeing your later comment about functional languages, I think they suffer from lack of overloading. E.g. F#'s fold2 would be much better as |> fold(a, %, func). Sometimes there's just no way around it.
I have never written anything in F# and I have no familiarity with the language, so I'm speaking more from my (IMO quite basic) familiarity with Haskell and Clojure, and most especially the former. In Haskell composition is just such a natural part of the design of programmes, and yes, functions often have many arguments. One just designed with composition in mind so this "piping" works really easily.
I think that’s my main issue with them. You shouldn’t need to write your code in a particular style to make a core language construct useful because of how often you deal with code that isn’t yours. Having to carry around a bunch of adapter functions kills any readability benefit.
As someone who will be pulling their hair for the first few years it will have to be transpiled into obfuscated spaghetti code (and will be debugging/understanding it): Why do we need an operator just so people can write unreadable code. Instead of using... descriptively named variables?
// A. Status quo (as per the proposal)
console.log(
chalk.dim(
`$ ${Object.keys(envars)
.map(envar => `${envar}=${envars[envar]}`)
.join(' ')}`,
'node',
args.join(' ')
)
);
// With pipes (from the proposal)
Object.keys(envars)
.map(envar => `${envar}=${envars[envar]}`)
.join(' ')
|> `$ ${%}`
|> chalk.dim(%, 'node', args.join(' '))
|> console.log(%);
// A normal person who is able to write good code (me :P):
const envVars = Object.keys(envars).map(envar => `${envar}=${envars[envar]}`).join(' ');
const coloredText = chalk.dim('$ ' + envVars, 'node', args.join(' '));
console.log(coloredText);
I'm not seeing it... can someone help me understand? Why are their so many proposals trying to force alien syntax into JS from "functional" languages?
As someone who originally would have strongly agreed with you, I think there is actually huge benefits to thinking in this "streaming" mindset.
That is, the first place I really encountered code like this was when Java introduced streams [1]. I wasn't used to programming like this when I first experienced this code, and at first I found it harder to read; it felt obfuscated.
As I got more used to it, though, I started to love it. There are tons of operations in programming like this, where you're really just performing a successive series of transformations on a set of data. Obviously functional programmers are familiar with this, but this is also the paradigm for shell programming, where you're just piping stdout to the next small little program in the chain.
The thing is, now I really feel a bit of a "switch" in my brain when I read code like this. That is, whenever I see this, I'm just like "OK, doing a whole bunch of data processing from some input set to a final output". Over time, when done correctly, I actually find this easier to read, with less verboseness, because my brain already has tons of context about what's going on.
OP's link provides answers[1] to that question. And in my
personal view, they aren't very convincing.
The first answer, tedium, is unconvincing, because pretty much
anyone with more than a couple of years of experience in
software development in a team knows that ease of reading
beats ease of writing.
The second answer, unexpected mutation of temporary variables,
seems to point to a deeper issue in JavaScript. That of
const not quite being const. Unless you Object.freeze
everything.
The third answer, that temporary variables require sequences
of statements instead of a single big expression, is more of
a feature than a bug, in my personal opinion. Statements
allow the reader to comprehend the algorithm one step at
a time instead of trying to untangle one giant expression.
To say nothing about the fact that a sequence of statements
is way more debuggable.
I'd say that JS is moving closer and closer to Perl in its
write-only-ness.
> The first answer, tedium, is unconvincing, because pretty much anyone with more than a couple of years of experience in software development in a team knows that ease of reading beats ease of writing.
Hah, I dunno, man. I guess it depends on how one defines "readable".
If having layer upon layer of indirection through inheritance, dependency injection, procedures split into tiny functions written in no particular order, and magic strings/numbers are considered readable code, then yeah, I suppose most mid-level developers prefer "readable" code.
> The third answer, that temporary variables require sequences of statements instead of a single big expression, is more of a feature than a bug, in my personal opinion.
Simplicity is much more important than even readability, though readability is still important. The more tools we add, the more rules and complexity need to exist around them, all while possibly adding little meaningful benefit.
In all honesty, I think that the most meaningful thing we got out of >=ES2015 is modules, promises and async/await, and maybe constants. Everything else became window dressing around existing constructs but introduced more complexity while also creating more rules. Things like classes were mistakenly implemented to satisfy Ruby and Python developers and to assist transpilers. The more I got away from using inheritance, the more I realized how unnecessary class syntax is. If you can just write a function that takes data and spits out data, then there's hardly any need for other class syntax features. If something seems to need inheritance, chances are you could have written your code to be more composable.
The more we try adding to JavaScript at this point, the more people are going to be encouraged to do things that make themselves feel clever to the exclusion of writing code that future devs won't have to spend hours deciphering.
A huge part of my job is deciphering. I don't know if it's like that for anyone else, but there's so much of it that it really makes me wonder how many millions, perhaps billions of dollars are wasted because engineers are trying to be clever.
Ah, missed that example. I am not seeing a single good argument
> Indeed, the benefits of method chaining are so attractive that some popular libraries contort their code structure specifically to allow more method chaining. The most prominent example is jQuery, which still remains the most popular JS library in the world. jQuery’s core design is a single über-object with dozens of methods on it, all of which return the same object type so that we can continue chaining. There is even a name for this style of programming: fluent interfaces.
Do they know what's so attractive about method chaining/fluent interfaces? They guarantee returning the same damn object/interface (tho the underlying values mutate). This pipe thing is complete opposite of that, what's the relevance here?
I think this is a good summary of why pipeline operators are long sought, more so than the proposal. If you are not fundamentally against chaining calls, there is no fundamental reason to be against pipeline operators because it can be thought as a generalization of chaining with similar pros and cons.
> I'd say that JS is moving closer and closer to Perl in its write-only-ness.
I've never understood why it's common to dismiss Perl as "line noise" while, outside of contrived or unusual Perl examples, it's way less line-noiseish than what I see in certain languages that are, evidently, very popular among people influential in the JavaScript community.
Implicit, repeated re-assignment of a magical "%" variable while adding a two-character punctuation operator to reverse the "direction" that values "travel" is clean and more readable? In what universe?
The example that also uses Underscore, especially, reads like some kind of parody.
> Why are their so many proposals trying to force alien syntax into JS from "functional" languages?
I don't know, why are there so many proposals trying to turn JS into an C#/Java like language? The class syntax, the private fields, TS with its interfaces and abstract classes.
The pipe will lead to more functions and function composition instead of fluent interfaces, which I think is more in line with JS.
I recently had to work with a JS codebase that looked like it was written by Java devs that were fooled by the surface similarities of TS and Java. Everything was a class, usually in a complex hierarchy with a lot of implementation inheritance. The code was deeply imperative and very hard to follow as well as being painfully verbose. It was the worst of both worlds.
You don't have to turn something into what it already is. Have you ever looked at the JS standard library String/Number/Math/Date? Heavily inspired by Java if not identical.
> “I was under marketing orders to make it look like Java but not make it too big for its britches. It’s just this sort of silly little brother language, right? The sidekick to Java.”
Sure, but JS code usually looks nothing like Java. People mostly write functions and data structures, just like in C. You don't start your JS code by writing objects.
> I don't know, why are there so many proposals trying to turn JS into an C#/Java like language? The class syntax, the private fields, TS with its interfaces and abstract classes.
Javascript's very OO, deep down in its bones. Its functions are even objects. It was just cursed with an OO model (prototypal) the distinguishing features of which pretty much all fall under "for the love of god, never actually rely on or leverage this functionality in any meaningful way" as far as how good an idea they are for code readability, and with god-awful scoping rules. "Class" and some other changes make the existing OO features usable in a way that won't have your successors cursing you for your "clever" use of prototypes, and saves tons of dumb boilerplate that never should have existed. It also makes it much clearer to the reader when you intend to treat something as an object, and when you're planning to treat it more like a struct.
I didn't mention OO in general and specifically Java and C#. JS is OO deep down, nobody is denying that, but it's not OO as in Java/C# where writing a function without a class is not idiomatic.
> "Class" and some other changes make the existing OO features usable in a way that won't have your successors cursing you for your "clever" use of prototypes
That's a strawman, you can write "clever" code with classes that will make your successors curse you just like you can write plain and readable code with prototypes.
> you can write plain and readable code with prototypes.
It's really far from being ergonomic, unless you're basically just using JS objects as structs. Otherwise you're dancing around its pitfalls and limitations—visibly, in code, not just in your head—to express things that are very simple and clear in pretty much every other commonly-used OO-supporting language.
> I didn't mention OO in general and specifically Java and C#. JS is OO deep down, nobody is denying that, but it's not OO as in Java/C# where writing a function without a class is not idiomatic.
Sure, JS is multi-paradigm, like tons of other languages. Its early design, and early use in the wild, hints at "Imperative with quite a bit of OO and a sprinkling of functional for things like callbacks" being more-or-less its intended style, but of course that can (and has) changed over time, because the core language has half-decent support for all three styles.
Incidentally, I've yet to see a TS codebase that ties itself in knots to avoid defining a function outside a class, to avoid treating functions as first-class when it makes sense to, or even to avoid a little imperative code outside of objects, here and there. They may exist, but I haven't seen it. I have seen a lot of code using HOFs to the point of absurdity, while passing big objects around to a bunch of functions and pretending that's "more pure" than putting those related functions on a class for that object. Or trying to shoehorn runtime-checked immutability into the language, at a performance cost for end users. Or writing their own weird, crippled form of objects in a language that already has them so they can keep things "pure" and let people keep avoiding the Class keyword for... whatever reasons they want to do that (React Hooks).
> It's really far from being ergonomic, unless you're basically just using JS objects as structs. Otherwise you're dancing around its pitfalls and limitations—visibly, in code, not just in your head—to express things that are very simple and clear in pretty much every other commonly-used OO-supporting language.
That might be because I've started OO with JS and mostly did some with it, but in my experience it's fine. I do tend to not use OO as in "object mutating themselves" much though, outside of cases where it's a great fit like a database connection.
> Incidentally, I've yet to see a TS codebase that ties itself in knots to avoid defining a function outside a class, to avoid treating functions as first-class when it makes sense to, or even to avoid a little imperative code outside of objects, here and there.
I haven't seen big codebases like that, but I've had a few collegues coming from Java/C# start every piece of code by writing a class, usually in a "kingdom of names" styles. I'm worried that TS may enable these people even more in the future.
> I have seen a lot of code using HOFs to the point of absurdity, while passing big objects around to a bunch of functions and pretending that's "more pure" than putting those related functions on a class for that object. Or trying to shoehorn runtime-checked immutability into the language, at a performance cost for end users. Or writing their own weird, crippled form of objects in a language that already has them so they can keep things "pure" and let people keep avoiding the Class keyword for... whatever reasons they want to do that (React Hooks).
I'm not defending "functional" (as in ML/Haskell) JS and agree with you here. If people want to do functional programming, there's OCaml, Elm, F#, Scala.js and ReScript, no need to shoehorn that into JS. I think that's an antipattern, just like doing OO everywhere for the sake of it.
Haha, cool, I think we're actually in agreement. I think JS works best if you embrace its multi-paradigm nature, and attempts to make it either Java or Haskell would be horribly misguided, even if those were both better languages, because the language doesn't really support the things that make going purely into either of those paradigms attractive—with or without TypeScript doing a little compile-time type checking on top.
JavaScript, in its bones, is a mix between an impure functional programming language like Lisp and a prototype-oriented OOPL like Self; forcing Java-style class-oriented OOP into it is a lot more of an impedance mismatch with its “bones” than FP constructs that are common in the kind of FP languages that influenced it from the outset (though neither is a giant mismatch.)
1) consts in JS aren't deeply immutable, so technically these could be mutated. This is a relatively small issue, but also...
2) More and more stuff happens in expression-contexts these days, including (most prominently) JSX. And JS doesn't let you define intermediary consts in expression-contexts (or even in lambdas, unless you build out the full curly-braces and add a return statement). So for many situations the consts version is arduous or even impossible.
1. Pipe proposal doesn't make anything deeply immutable either. In JS you can even use a curly braces block to avoid leaking your let/const variables.
2. I am dreading even thinking about how the pipe thing is supposed to operate on JSX. There's the do-expression proposal to achieve what you want in a sane way: https://github.com/tc39/proposal-do-expressions
1. It doesn't, but mutation is generally less likely to happen in an expression context than in a statement context (partly by convention, but also it's common to eg. lint against assignments in expression contexts)
2. Personally I think it will be quite elegant. JSX itself is an expression, not a function, so it would only ever be the last stage in a pipe (or the context in which a pipe expression is embedded). Note that it would be useful even for just (for example) string transformations that happen to be deeply embedded in some JSX, not exclusively contexts where JSX is directly involved
2b. I didn't know about do-expressions; those look potentially exciting
Yeah but if you just write plain old readable code that doesn't use esoteric syntax sugar, then the problem is that almost anyone can understand it without having to google stuff.
A lot of the criticisms here show that many of you haven't worked with functional programming languages. All of which have pipe functions. This is fine, you can be an amazing programmer without ever touching functional programming. However, be a little bit more open to it, functional code is just another tool to use like every other tool. For some problems it's *the* solution. For others, it makes everything more complex.
This doesn't make code less readable, you're just not used to reading code like this.
After a week or two you realize how awesome this is. Piping has been a part of consoles forever now, what's wrong with bringing them into code?
I couldn't agree more. I feel like Hacker News is turning into a place where, especially for discussion of "popular" programming languages, the commenters seem to be largely those who don't have a solid foundation in the specific topic that's being discussed, but seem to believe they're experts because they've written a lot of code and have strong opinions on what code should look like.
Conversations about more esoteric languages seem to attract the level of discourse HN is otherwise known for.
>Hacker News is turning into a place where, especially for discussion of "popular" programming languages, the commenters seem to be largely those who don't have a solid foundation in the specific topic that's being discussed, but seem to believe they're experts
This is just HN in a nutshell.
I've been wanting to write a blog post for a while about this topic. I have a pet theory that the HN 'feature' of not implementing direct comment reply push notifications (ostensibly a feature to dissuade flame wars) leads people on HN to make very strong statements they often can't defend.
I think it's ultimately a net negative for the community. I get not wanting flame wars, but when you make strong statements on platforms that have the tools for better comment section engagement, you often have to defend your statements. I think this is a good thing.
Totally agree. I commented elsewhere that I had someone of a similar reaction when Java introduced streams, in that the common paradigm is to just call successive operations on the stream you're processing.
It took a while for my brain to get used to it, but once it did I found it so much easier to read, and much more efficient to write.
I think this is obviously a useful tool and yet I worry that JS is going to end up in the C++ hole -- too much syntax for most people to remember, having to carve out practical subsets for readability.
I'd argue, the too-much-syntax threshold has been passed back
when destructuring assignments[1] got in. Now you can witness
“beautiful” code like:
const [,, { name }] = props;
and:
const { $ } = window;
You can kind of reason about what's going on here, especially
if you're a fan of one of the fancy languages with pattern
matching, but if you're just a lowly backend developer in one
of the “boring” languages, and you just need to fix this one
thing in this one JS file, you're not going to have a good time.
It's not difficult to understand, you just can't be bothered to care.
In the 7 years or so that destructuring assignment has been available, I've never once seen the 'empty destructuring' in your first example. (And if I did, I'd most likely tell someone to just do `const { name } = props[2];`. The second example is a bit silly- if you're using jQuery that much, autoprovide it using a transpiler and be done with it.
My guess is that your org just can't be bothered to care that much about your frontend, so they slap something together that's difficult to maintain, and then blame the language, execution environment, or community for poor results.
In the other words, if some feature can be recursively composable and a weakness of the feature appears only when highly composed, it's generally not a fault of the feature itself. Of course it can still be argued that an empty destructuring (itself non-composable) should be made invalid.
While it's easy to go overboard with destructuring like
function foo({
quux: {
foo: {
bar: {
baz: [first]
}
}
}
}) {
console.log(first);
}
(I've seen things like this), that's what code reviews are for. I find destructuring one of the features that made reading (and writing) JavaScript actually tolerable.
I’ll argue further suggesting Typescript has added to the too much syntax conundrum. Modern JS looks nothing like sensible syntax, and we are definitely in the realm of C++.
But alas, no one ever listens in JS. They just add more and more shit until things are unrecognizable.
People hate plain JS so much, that they’d rather mutilate into the language they prefer, and people are coming from every corner of programming with every type of preference (pun).
One of these days I’m going create a barbarian horde of JavaScript developers and inundate another language, maybe Java, maybe Ruby or Python, and just vehemently insist that they must change their idiomatic style to that of Javascript. Let’s see how they like it.
Anyone know why we have 30 imports at the top of each file like it’s Java? Where did that idea come from? Excessive types, huh? Since when? Have mercy on us, please.
Edit:
These were the welcome and necessary changes IMO:
JS just really needed Classes as syntactic sugar for the odd way we replicated it with prototypes. That and the fat arrow (because binding ‘this’ was cumbersome), along with a basic import statement. Destructuring I can live with. Async/await was also necessary. We really didn’t need shit else.
Small exception:
I’ll make a veery small exception for a minimalist type system, or in other words, only 10% of typescript. I’ll take just the ‘Type’ and that’s all.
Last note:
Coffeescript did a better job synthesizing JS with ideas of other languages compared to ES6 honestly. Sometimes you have to think about how things mix/fit rather than ‘oh this language has it and JS doesn’t, so let’s add it’.
As someone who had the pleasure to write plain JS since, say, 2003, I greatly welcome all these changes that make JS less repetitive and easier for writng the correct thing.
And JS codebases are so much larger than in 2003 that the old idiomatic style is just unproductive.
> I’ll make a veery small exception for a minimalist type system, or in other words, only 10% of typescript. I’ll take just the ‘Type’ and that’s all.
All we should have are primitive types like object, number, string, boolean, undefined. Anything else should just define a shape/interface of an object, but no "type" that can be inherited.
In that case, as you say, 90% of Typescript could go away and be relatively straight forward.
> JS just really needed Classes as syntactic sugar for the odd way we replicated it with prototypes.
Depends on if that's actually odd. In my opinion, we should avoid prototypes most of the time, so it's strange in retrospect that we layered a cumbersome OO construct on top of it without any of the benefits other languages have like being able to change the constructor/initializer method after class construction.
> We really didn’t need shit else.
Yep. Sadly the older I'll get the more crystalized that realization will become and the more frustrated I'll be at a world that thinks more is better.
“10% of TypeScript” sounds like words uttered by someone who has never used typescript. Strictly typed programs are incredible. My UIs never crash anymore
I disagree. I didn’t start programming with Javascript, in fact I started with your typical compiled languages that literally need types to allocate memory. Even in those languages, I haven’t seen that kind of excessive use of Types that I see in Typescript. I’d love for more people to chime in on this because I was taken aback at how exhaustively Typescript is used by the JS community (different from widespread use, I mean people are literally going overboard with how far they are going with the type system).
I’m advocating for a tighter, more minimalist language. It’s not what you put in, it’s what you leave out, remember?
What kind of programs do you develop in JS? I don't think the kind of low-level programs that are allocating their own memory are anywhere near the same world that JS UIs are, and TS really helps solve JS UI problems like deeply nested JSON access, fully-typed state transitions, and manipulation of component interfaces (i.e. passing partials around so the "user" controls labeling while the component's developer wired functionality).
What parts of TypeScript are you finding would impart more value by having been left out? For example, I find it extremely expressive, and I have gotten huge value out of things like accessing return types of functions, aliasing the non-nullable types in a union type, creating sub-types of massive JSON structures via `subtype = MyType["mySubField"]`, Partial/Pick, Exclude/Omit, ... What things have you seen that you don't understand the usage for?
Final question: what is the most sophisticated UI component you have ever implemented? For example, I regularly implement what are sometimes referred to as "dataflow editors", so maybe this helps explain why I see value in the more niche sides of TS while you come in and decry the construct entirely
I don’t know anything about what you are working on, if you say it works for your specific use case, fine. A better question for you would be, do you believe you could not write that UI if Typescript didn’t exist? You’d be able to, but I know you’ll argue ‘but it would be much more painful’.
Maybe, maybe not. But to entertain you, if the breadth of Typescript solves your niche needs, do you believe most UIs require that same exhaustive usage? Because that’s what’s happening, I’ve seen very simple things overusing it.
I don’t have much more to say about this, but happy to hear your last word.
> I don’t know anything about what you are working on, if you say it works for your specific use case, fine. A better question for you would be, do you believe you could not write that UI if Typescript didn’t exist? You’d be able to, but I know you’ll argue ‘but it would be much more painful’.
I worked on a team that happened to be starting on a cross-platform React Native app that needed to have its business logic able to be re-used on a couple other platforms that weren't well-supported by React Native (or React), but did support JS well enough, when we decided to trial TypeScript. The team had done a bunch of "apps" in React and a couple in React Native without TypeScript, before this.
TypeScript made things so much nicer. It cut necessary communication overhead during development down to almost nothing. Turnaround on platforms that were slow to deploy to was a non-issue because things almost always worked on the first try. Navigating the codebase was downright pleasant. Refactoring a library that was used by multiple platforms, a breeze. Simply excellent.
I am working on various UIs for data manipulation & visualization.
> Do you believe you could not write that UI
No, I wrote these UIs in bare JS and React before, using class-based components and old react state or mobx. I also maintain a library of components because I generally write multiple UIs simultaneously, which of course can use the same building blocks. Since React Hooks & TypeScript, my component library lost about 40% of the LOC (after adding type definitions), looks much prettier, and is infinitely more discoverable with type annotations and autocomplete on everything.
So, do I need it? No, you arguably never need type systems. But the developer experience is an order of magnitude smoother. I option-click into any component when I need to understand its type interface, and I don't need to read any code to do that! I also can seamlessly use graphql-codegen.
But maybe more to the point:
> I’ve seen very simple things overusing it
This is true of every construct, ever, do you not think? People are not inherently good programmers.
Ultimately, I admit that TypeScript and JS have design flaws and non-minimalist features that you can attribute to "dumb evolution" (i.e. people adding features here and there to JS, TS trying to ensure all sorts of JS are covered, TS trying to make sure you can handle all existing types of JS with TS, etc.). However, given the context being that JS & React/JSX are the dominant UI language for web, TS solves this use-case cleanly and with a great level of expressiveness. It is huge and can be overwhelming, but it's important to remember you don't need to use any of these things, and there isn't really. any sort of interop-gulf around TS either (i.e. someone exports a type, you can interact with it, no questions asked).
So, is it graceful from an idealist perspective? No, I concede this. However, it is a tool I would never again turn away from, and while I wouldn't necessarily say it enables me as an individual to do new things, I think it makes it possible to program ideas of high complexity and allow others to work with it much more easily than just JS, which enables me as a team member to boost my teammates.
Reading one of your dependencies to track down a bug, and finding that they've written it using a totally alien subset of the language that you don't use, is not a lot of fun. The JavaScript community in particular both loves dependencies and loves experimenting with subsets of the language, when they're not embracing libraries that let them shift wholly into idioms from some other language. So as fun as it might be to write in some language that had these from the start, I'm not thrilled about giving folks a dozenth way to accomplish the same thing.
I already feel that way with some features of ES6 and typescript - though both have more benefits than negative attributes, sometimes there are more than one way of doing things, which introduces a little mental overhead when reading code, and the overhead compounds over time.
For example, types vs interfaces in typescript - they can be used interchangeably in many cases, and not in a few other cases. [1]
Let's KISS, not over complicate for the sake of more features that may be negligible in value. And let us especially not introduce features that can overlap.
This is a very lightweight addition, with a huge quality-of-life impact, and one which must be immediately appealing to everyone, not just the FP crowd.
C++'s syntax problem, to my mind, is not that it's excessively large, but that it's excessively clever and error-prone, and sometimes not very uniform. It's hard for humans to parse.
This proposal does not introduce any special cases, it adds one (or two, if |?> is also accepted) very clearly defined operators, proven by practice. It makes parsing things by humans easier.
I've said the same a few years ago on Reddit and got downvoted to hell. JS due to maintaining backwards compatibility is already huge (syntax-wise) and is going to grow even bigger.
In response to people repeatedly advocating for F# + partial application over Hack, TC39 responds with what is essentially a threat:
> To emphasize, it is likely that an attempt to switch from Hack pipes back to F# pipes will result in TC39 never agreeing to any pipes at all. PFA syntax is similarly facing an uphill battle in TC39 (see HISTORY.md).
While I can respect differences in opinion, this feels heavy handed.
We've suffered under "bad parts" in JavaScript before, and the solution was not to use them, which works out fine for the most part. But I tend to agree, adding a functional language feature built in a way that conflicts with Ramda and nearly all existing FP libraries is no better than simply using R.pipe.
I personally prefer F# pipes as it leaves the door open for partial application in the future, whereas Hack pipes closes that door because any partial application would be duplication.
However, I feel like all the people using Ramda should just make their own language if they don't like the direction JS is going in. This is a "too many cooks spoil the broth" situation.
Have been following this since 2015-2016. The proposal got bikeshedded to destruction. What we wanted was a SIMPLE pipe opearator that pipes preferrably unary functions and composes them to a single expression.
Then began the sheedding.
Why does pipes haveto deal with async? Async is 99.9% IO realated, and now it has to be shoehorned into something thats by nature sync.
On top of that now theres a magic variable so you could pipe functions accepting multiple params.
He beauty of a pipeline is that its naive, and forces a certain code style, makin testing more easy, and code more elegant.
Today im on the fence, as im not liking what the proposal has become.
What's the purpose of a pipe operator without stressing the need for a composition operator? So often I see a function with the body return it's input parameter piped. That's just compose with extra steps.
The current JS argument order is a mess too because you'd ideally want your collection last so you can partially apply the parts you'd normally apply and compose transformations for that collection, but most function arguments aren't in this order. Elixir is the 'weird' language piping the first argument instead of the last, so I guess that is a solution, but it doesn't fit the 'normal' compositional style from ML-family languages. This might be a requirement for languages with variadic functions.
The spec points to the `%` placeholder-y thing from Hack which shows how weird piping gets in an uncurried language like JavaScript. I think these issues illustrate how nontrivial adding piping would actually be in JavaScript.
Currying is the obvious, simplest solution because it's always one parameter in and only one thing is returned (often a function awaiting one new argument). But this would require massive fundamental changes to the builtins probably requiring something like "use currying" to tell the user agent to flip the argument order and curry all the functions. I'd be pretty into this though.
> But this would require massive fundamental changes to the builtins probably requiring something like "use currying" to tell the user agent to flip the argument order and curry all the functions. I'd be pretty into this though.
Could you just clone them and expose versions with swapped arguments under a different namespace?
I'm familiar with currying. In the real world, unless you have a certain type of parse transform, that's not the same as composition, due to lambda overheads.
Currying is the answer to your question, regardless of any possible real world overhead, which can be optimized out. This is a proposal to modify the language we're discussing, so I'm assuming they can do the latter as part of the proposal if that were necessary.
Given
f :: a -> b
g :: (b, c) -> d
h = uncurry ((curry g) . f)
It isn't just "real world overhead", it's the ambiguities or pessimal behaviors you encounter if you try to bodge currying on to a language that wasn't designed for it.
For currying, for instance, you need to know exactly when to execute the function and return the results, vs. when to return another curried function expecting more results. This is messy in the face of optional arguments, and in Javascript, all arguments are always optional.
"But I can solve that with more syntax!"
Perhaps so. But then you have the problem, what if you want to take advantage of optional arguments to add another argument to a widely used function? Do some of your invocations of the function that previously considered the function "done" now return functions expecting one more argument? Probably, unless your syntax forced explicit specifications of when to curry. Dynamic languages kinda have that sort of problem all over the place anyhow, but this would be a new manifestation for people to deal with. That's getting ugly and complicated.
You're probably better off just using the lambdas the language already has rather than blundering your way through this mindfield. I don't think I can name a single case of a language solving this problem after the fact; by all means upgrade my knowledge by naming one that did (no sarcasm, I'd be interested in seeing it).
FP-like syntax is hardly possible to make ergonomic without automatic currying.
Which, I suppose, is out of question. It's hard to introduce in a backwards-compatible manner.
Why, I won't mind if MS developed a variant of Elm instead of TS. But that would be not realistic for a number of reasons. You can only turn a supertanker slowly.
> However, nesting is difficult to read when it becomes deep: the flow of execution moves right to left, rather than the left-to-right reading of normal code.
Personally I do find lisp code terribly difficult to read because of the inside out nature of it. For tricky Lisp I used to use a tool that would generate control flow graphs for me to make it easier to grok.
Does anybody more involved in this topic know how high the chance of this getting implemented is? It mentions that this has been rejected twice before, mainly for runtime optimization reasons, and that there is another proposal [1] that mentions this as an extension to its functionality.
If your concern is that this is additional unneeded syntax that is just going to result in difficult to understand, overly terse code, my 2 cents is that a conversation among your team to discuss these cases and agree on some standards is a pretty straightforward solution. It's not like today's JS has the Python philosophy of "one obvious way to do it".
I'll throw my hat in with those on the sidelines excited for the language we're stuck writing more of than we may want to, getting another piece of syntax that improves the ergonomics in a lot of little cases. Much like destructuring, much like the optional chaining and null coalescing. It will be nice to avoid the occasional temp variable that isn't useful for readability, or to rearrange expressions that read better applied than composed... but mostly I'm looking forward to how much nicer a pipe operator makes iteratively doing something in the console.
The decorators proposal has been stalled out for years! It's actually a bit scary as Typescript forged forward with the original proposal (Angular, NestJS apps have almost cemented these in many prod apps). So there stands to be a lot of breakage if the newer proposal is adopted. I'm sure there'll be codemods, but this is what happens...
I never understood why JS needs decorators. It has pretty flexible function syntax already, so decorators wouldn't clean nearly as much code up as pipes would.
Right, and it definitely wasn't a comparison between the two proposals. I love OCaml/F#... and Elixir. So the pipes are cool with me. I've never tried the Hack variant however.
As far as decorators as a "need". You're right. It's often as easy as `functionName(...args)(target)`. The decorators are definitely convenient for those coming from other languages that have annotations though.
Okay, from the other comments, I gather this is like Hack. Does any other language use a pipe operator for non-unary functions? Does the `%` syntax apply more generally? What is the type of `foo(%, 1, 2)`? One would expect it to be the same as `placeholder => foo(placeholder, 1, 2)`, but is it?
For example, in Scala, if I have `foo(Int, Int, Int): Int`, then `foo(_, 1, 2)` has type `Int => Int`. That's true anywhere, but in the JavaScript proposal we seem to have a syntax that only applies in a special case.
How about the null coalescing operator that literally has zero use outside of encouraging an anti pattern in failing to disambiguate null and undefined at the earliest possible opportunity?
How the fuck did that ever make it into the language?
Some of the upcoming proposals are absolutely ridiculous too. A lot of these are ideas that would be fine for niche use cases, but the proposal itself has examples so stupid that they should be shot down immediately because the person doing the proposal doesn't know what the fuck they're doing (e.g. the ridiculous jsx example in the 'do' operator proposal where for some reason we now want to shoehorn a block of procedural code into a 50 line declarative expression instead of just using a basic one line ternary operator that has been in the language for years).
I realize they haven't committed to '%' as a final choice for the placeholder, but I'm wondering if there might be any ambiguous situations with the modulo operator. Perhaps the places where an operator can be used are sufficiently distinct from the places a placeholder might be used for that not to be a problem, but it bugs me a little that this is not addressed.
This is interesting. Pipe operators are a big win in declarative languages because they allow you to implement a pipeline of operations, which goes against declarative semantics. Sequencing is difficult to express declaratively, thus pipe operators.
But imperative languages are really good sequencing! The syntax for doing this in Javascript is ;
Maybe pipes will give you some syntax wins, but it's not a huge win the way it is in declarative languages. I feel like what we are seeing in general is a lot of the good ideas of declarative languages making their way into imperative languages, sometimes without the best rationale. I'm not really seeing anything compelling in this proposal. It focuses a lot on legibility, but is that the biggest win here?
And is it an unambiguous win? If you're going to go through all the trouble of adding this thing, it better be fore a really good reason with clear benefits that outweigh the costs. I worry that with some of the examples they give (probably the best cherry-picked ones they could find), I find the temporary vars version more understandable. Yeah there are fewer keystrokes, but I have not the slightest idea what this does:
Also, another reason this is not so great in Javascript is because the functions are impure. In pure functional languages you can chain functions together safely and with certainty because you know:
1) input arguments are immutable and will not be modified by the function
2) The function has no side effects.
Therefore you can run these pipelines multiple times with the same input and you know what output you're going to get.
If you have one or neither of these guarantees, then it's the wild west. What happens to the arguments when they go into that function? Are they mutated? Are they shared with another thread/process/worker? Are there any system calls in there? File IO? HTTP Requests?
With this proposal, you have all these side channels that can be used and no guarantees about mutability or idempotency, or even whether or not the variables will have the same type after they are passed into a function. This just seems like the makings of a huge mess to me.
Interesting, coming from Clojure/Script it bothers me that you need to repeat the pipe since I'm used to:
(-> "a b c d"
s/upper-case
(s/replace "A" "X")
(s/split " ")
first)
But I guess in JS you wouldn't know when to stop the piping.
Also, in Clojure, you have -<> which is like a hybrid between Hack and F# pipes. Basically it defaults to piping into the first argument, but if you use <> anywhere, then where you've used it is where it will be piped instead, best of both worlds.
Sorry, this may be a dumb semi-related question, but on that proposal there is a link to "What do you feel is currently missing from JS?" from the 2020 State of Javascript survey, and just below the pipe operator is "functions". Does anyone know what that is referring to? Obviously JS has functions so I'm not sure what the context is here, and I couldn't find the original questionnaire that potentially had more details.
As a comment above mentioned, it means actual functions which js current has, meaning the incredible minority of the people that voted on that question, don't know what's going on with js or are just trolling.
Please https://news.ycombinator.com/user?id=BrendanEich can you become a member of the ECMA committee before it's too late. It's getting taken over by some functional programming cult. Turning your beautiful (tho quirky) creation into something it's not meant to me.
I think that was more a function of being the only browser language.
The target of JS wasn't pro developers in those days anyway, so familiarity wasn't really a consideration.
Most importantly, all the "new" features of JS would have existed for 25 years now. JS would have been fast in 1995 instead of waiting until 2008 to be usable. We've take years to add simple things like integers or arrays to JS that would have just been there. We're still debating things like threads which should also have been a solved problem by now. If you didn't like scheme, compiling to the language from another would be trivial too.
Back in 2003-04, around the time of Gmail's launch and the inflection point of the massive explosion of DHTML evolving into Web 2.0, about 95% of internet traffic was using a browser that did support a certain second <script> language other than JS. It really could have gone differently.
>I think that was more a function of being the only browser language.
Sure, that doesn't hurt.
>The target of JS wasn't pro developers in those days anyway, so familiarity wasn't really a consideration
Lisp like syntax is very peculiar. It puts off novices and pros alike.
I didn't really understand you last paragraph other than agreeing that integers should have been there from day one. Array-like objects are not so bad.
`_.reduce(list, function (acc, num) { return max(acc, num); })` vs `(reduce list (fn (acc, num) (max acc num)))`
Very few devs who code in lisp for 2-3 months come away hating the syntax. Hating something strictly because it is different is not a great life perspective IMO.
> I didn't really understand you last paragraph other than agreeing that integers should have been there from day one. Array-like objects are not so bad.
Scheme has a lot more than integers.
* Rational numbers, Complex numbers, integers
* vectors (real arrays)
* Linked Lists
* Proper Tail Calls
* Lazy evaluation (the basis of things like generators, iterators, and the event loop)
* No duck typing
* No weird evaluation rules
* Fast implementations. The fastest like Chez Scheme JIT are very fast compared to v8.
* Macros for new features
Then there are all the common SRFIs standards that pretty much every big-name scheme implements.
* Typed vectors (specify primitive array type)
* Multi-dimensional arrays
* Hash Tables/Maps/Sets
* Multiple value returns
* Records (structs)
* Objects
* Extensive String functions (way more than JS currently offers)
* Multi-threading
* Streams
* And at least 200 more features
Think about ES6+. The features fit into two categories. The first is primitives (TypedArray, Set/Map, const, etc). The second is syntax (class syntax, destructuring, generators, arrow functions, exponent operator, etc).
Those "new" JS primitives have existed in Scheme since the 70s and would have existed in the browser for 25 years now instead of a handful of years. The second class range from unnecessary to already present in macros to easily implemented in macros. There would be no need for transpilers; we could have grown the language without needing all those backward-incompatible language releases.
CSS would be different. The DSSSL proposal (literally scheme) would have replaced the current nasty C-like one.
Likewise, we wouldn't have suffered through XML waiting for JSON to arrive. JSON's syntax is just another expression of lisp lists. `{foo: [1 2 3], bar: "abc"}` would have been immediately obvious as `((foo . (1 2 3)) (bar . "abc"))`. There wouldn't be an extra JSON parser because this is built into the language. XSLT is just very bad lisp macros, so even that excuse would not exist.
Given time, we might have even seen lisp-style lists replace HTML with `<div class="foo">123</div>` becoming `(:div ((class . "foo")) "123")`. If you think about it, this is just `React.createElement("div", {class: "foo"}, "123)`. As this is also dead-obvious, we would have had much better JS frameworks decades earlier and without nearly as much framework churn.
Wasm also wouldn't really be a thing either as Scheme would likely be fast enough and a very reasonable compile target. Type hints like what Common Lisp uses are already very fast (around Java performance and that's with just a couple (amazing) hobbyists rather than a huge, paid dev team). I would note though that wasm uses s-expressions in non-binary form.
This isn't just theoretical. When Paul Graham said Common Lisp was their secret weapon in the 90s, this is what he was talking about. Today, using Common Lisp, you use CL-WHO to turn those s-expressions into HTML. You use parenscript to turn S-expressions into JS. You also use something not too unlike DSSSL to turn s-expressions into CSS. It's lisp all the way down to SBCL which compiles s-expr assembly into binary for the computer to execute.
There are even more results. Initially, Steve Jobs only wanted web apps on the iPhone. It turned out that JS just wasn't fast enough at the time. If Scheme had been the language, the web would have most likely been fast enough for almost everything. The current stagnating Mobile Safari and app store lockdowns would probably have never happened.
Scheme instead of JS would have dramatically altered computing as we know it and I think it would have been for the better.
I agree that Lisp’s “no syntax” is an acquired taste, but asking someone to keep an open mind and use something that peculiar for ≈3 months is a very tall order.
Most people look at the curtain of parentheses and reject it at the spot.
I’d love to have had Scheme on the browser, but I’m not sure it would have succeeded.
Seems to be trying to solve a problem that I would consider an anti pattern (attempting to 1 line everything). Someone else mentioned temporary vars and I think thats appropriate and equal if not better syntactically.
This example:
// Status quo
return filter(obj, negate(cb(predicate)), context);
// With pipes
return cb(predicate) |> _.negate(%) |> _.filter(obj, %, context);
Should in my opinion be written as:
let result = cb(predicate)
result = _.negate(result)
result = _.filter(obj, result, context)
return result
The main benefit of this is its experience and almost language agnostic. You see a variable defined and it's run through a series of functions before being returned. What's the point of removing a few characters?
edits: figure out how to format code, don't omit things
You're mutating result every step along the way and passing it into the functions as well, such that if it's an object or an array it could be mutated in non-transparent ways. The main benefit is to encourage a more functional, immutable data flow.
I get your point but I guess I fail to see how more obscure syntax at the time of composition helps someone understand something should be immutable. If I'm mutating inputs in a functional programming code base, syntax probably isn't the problem.
Your suggested code and the “status quo” code are not equivalent, and the with-pipes version isn't the conversion of either of the other two to the proposed pipes syntax.
Notably, on the first point, you proposed version drops two input variables from the other two versions, and on the second, the pipes version would correspond to the “status quo” version if the “_." prefixes were dropped.
It looks like you've made your proposed version look cleaner by both omitting required elements from it and adding unnecessary (and code-breaking) elements to the with-pipes version.
Interesting data point, my impression was that they were used quite a bit in Haskell but I’m no expert. They are ubiquitous in Python, providing an extremely expressive means to construct lists, sets, maps, and generators. JS map and filter are exceedingly crude and unergonomic in comparison. Rust does it with a sophisticated iterator API.
They seem to be used mostly in like beginner tutorials as examples of crazy cool stuff haskell can do.
I actually just recently saw it in production code for the first time and was like "whaaaat is that?" It had been so long since I saw one that I didn't recognize it at first.
The thing is, usually in real haskell code you aren't going to be using something that is roughly like set builder notation. Well at least, I haven't seen many scenarios where its needed. You're pulling data from someplace, manipulating it, etc, and then sending it somewhere else.
That's not to say that I'm absolutely correct on this or whatever. But to me, overall I think Haskell syntax is far too complex and has too many special cases, and given list comprehensions are exactly equivalent to doing the same thing via the list monad, I'd prefer they be gone.
I fear that someday we'll collectively need to replace GHC, and each and every weird edge case just means that its that much more of a giant task when that day comes.
And GHC has list fusion rules in this case (not sure if they exactly apply for list comprehensions; they probably do, but i'm not certain of that.)
so IIRC the xyntax in haskell syntax your example would be smth like:
[ manipulate x | passes_filters x, x <- producer_stream ]
which is transformed to:
do
x <- producer_stream
guard (passes_filters x)
manipulate x
Which I believe does not have the same run-time performance as my fmap/filter example; even if it does, I'm just saying I don't see it often, and its not idiomatic.
Its different in python though because, well, python is more cursed.
Right, thanks. Don't get me wrong, I like Haskell (though haven't got good at it yet; keep getting lost somewhere between fmap and applicatives/monads).
However, I prefer the comprehension and `do` version over your `fmap` expression. The reason is that I think that filtering and transforming a stream is sufficiently common/important that a language should provide declarative syntax / a DSL for it. Whereas your `fmap` version makes too many implementation details explicit: all that should be needed is for the programmer to say "lazily filter and transform these values". I think it would be ideal for the programmer not to have to worry about `fmap` and `$`, and I think the expression should read more naturally than your fmap example.
The Python and Haskell comprehension versions pass the "read naturally" test very well: "create a set/stream of manipulated items where the items pass the filter".
The Haskell `fmap` version reads as "apply the manipulate function to a functor where, oh by the way the functor is a kind of list, and oh by the way use $ to separate arguments in this expression, but as I was saying do that but filter the list first".
EDIT: I don't mean to imply that I know how Haskell should be written, I defer to you; the sophistication of Haskell's functor-related abstractions is such that I bet you're right that the special case syntax of a comprehension is kind of an annoying presence in the language (but that still leaves two ways to do it?). But, we were talking about javascript and there I think it makes a lot of sense to give the world a way to construct collections declaratively.
This is all just my POV too. I'm sure there are people out there who use list comprehensions all day every day. I just haven't seen them.
I tend to vastly prefer a small, beautiful, principled, "growable" language over one that has many builtin forms. e.g. Scheme. Because, as I said, each of these features just makes it harder for other implementations.
FWIW I also don't think it made sense for JS to get a pipeline operator; only reaosn I like those is when a language supports user-defined operators. But, anyway.
Given the examples, the proposal seems overly-fixated on the ergonomics within JSX, which isn’t JavaScript. It seems more appropriate to just extend JSX so as not to introduce yet another layer of syntax to JavaScript (which is too complex as it is, in my opinion).
IMO pipe operators wouldn't bring a big improvement from looking at the examples, some are becoming harder to read for me and I think temp vars would clean them more, in any case I would probably use them as they make code fun in some places.
It would be nice to see more functional features, similar to async/await, template literals and lambdas. Some things that I think would be interesting are optional type hinting (native), exhaustive pattern matching.
Furthermore, I'm continually astounded by the naysayers who are opposed to language evolution as a general principle. I read these sort of objections all the time and all I can think is that if we took the "change almost nothing about our tools - think of the juniors!" approach advocated by these folks, we'd still be writing C and the industry would be an order of magnitude smaller than it is. Software development is a unique field of endeavor where it is remarkably easy to build and then use new tools, and while it does lead to lots of stressful change, it also leads to our discipline accomplishing more and more as the years pass. Thankfully, the silent majority seems to understand this just fine, which is why despite the tenor of the conversation around these (and other) parts, our tools do continue to get better and better as the years go by.