I like goofy projects like this one but I think that if you insist on having calls there, then might as well make it more explicit with something like this:
Where `p` stands for Proxy to the previous result. Under the hood it would just return an object describing the name of the called method and arguments passed.
The pipe function would then iterate over the array, calling methods as described.
It's not immediately clear what should happen when a method's return type is a function, but I suppose this can be handled via convention.
Yeah, I did something similar in effect in Python some time ago: https://github.com/vollcheck/pypelines - this is a bytecode hack rather than operator overloading.
And call it a day. It does about 80% of what a pipe macro would do, has instant compatibility with other prototype methods and leads to very simple types. The only issue is that you have to define lambdas to call multivariable functions. D has got this very, very right.
or some other name instead of `pipeTo` should there be a conflict.
JS already has a few well-known symbols like Symbol.toPrimitive that allow one to modify an object's behaviour (in this case what happens when `valueOf` or `toString` is called), so there's precedent.
It's certainly a nice usage of JavaScript's Proxy object and the resulting syntax is simple to grasp. Also glad to see the TS definitions in there. Well done!
For those that don't know. In Clojure it is also perfectly valid to drop the parantheses for each subsequent call in a threading macro if you don't want to pass in any additional arguments:
(-> x f g h)
If you need to pass an additional argument to g you can always do:
(-> x f (g foo) h)
There are thread first ->, thread last ->>, and even a thread as "as->" depending on where you want to place the argument when you pipe the result through the thread of functions.
It feels a bit unnatural to pipe multiple arguments like that in JS, and you could inline them in the first call without losing legibility:
a = c(b, 7) |> d(%)
It's the arguments added way down the line that are problematic:
a = f((e(d(c(b))), 4), 5)
a = ((b ~> c ~> d), 4 ~> e), 5 ~> f
a = b |> c(%) |> d(%) |> e(%, 4) |> f(%, 5)
Smart pipes [1] were a bit nicer about that:
a = b |> c |> d |> e(#, 4) |> f(#, 5)
Basically you would use # token whenever you wanted an expression and just a function name when you needed to pass a single argument. Best of both worlds IMO.
Perhaps it could even be extended for Curry-ish function syntax (although it isn't as obvious):
> P. S. When studying Haskell on a CS course in school, I used to define exactly the same squiggly arrow operator for my programs :-)
Haskell base already has the reverse application operator (&) which does the same thing. Not included in the prelude but can be imported from Data.Function. [1] It's defined as:
(&) :: a -> (a -> b) -> b
x & f = f x
Not that there's anything wrong with using your own definitions, just thought I'd point it out!
That has 4 opening parenthesis and 5 closing parenthesis.
b is passed to c
b ~> c
then the result is passed to d
b ~> c ~> d
then to e along with 4 as a second parameter
b ~> c ~> d,4 ~> e
and finally to f along with 5
b ~> c ~> d,4 ~> e,5 ~> f
The nice aspect of "this ~> that" is that the meaning is simply "put this into that". And that it results in the shortest code.
An alternative would be:
b ~> c ~> d ~> e(%,4)
Slightly longer, but maybe easier to read. Also easier to handle, as you can remove the fourth part " ~> e(%,4)" without having to change the third part.
I've used the Remeda package to preserve type information in pipes in Typescript: https://remedajs.com/. There may be better implementations (fp-ts?), but I like Remeda's docs.
Just looked at the examples. To me every one of them makes the original code less readable. I would also encourage people to not get used to this style because now you are stuck on some weird JS style and will have trouble reading code in other languages.
The "vertical" example looks contrived in JavaScript, but if that's your thing, I think it's OK.
But please don't promote antipatterns from languages that actually have a pipe operator (like Elixir) such as the '''concat("!")''' where the previous implementation's version using an operator is much clearer and more idiomatic.
I see a ton of Elixir code where people shoe-horn things into a vertical pipeline where the "normal" code would be a lot more readable, for example invoking '''Kernel.+(2)''' instead of just doing '''my_var + 2'''
Nice idea, there is also @babel/plugin-proposal-pipeline-operator which would require much less changes in the code if pipeline operator is introduced in the future. But it requires working babel.
This solution seems a lot cleaner to me syntactically, but it does lack helpers like .concat(). You'd have to write methods like .pipe( x => x + "!") to extend methods. Then again, prefixing and postfixing operaties shouldn't be too hard to write either
I mean, if you abuse formatting enough I suppose... although I will say, the pipeline/bind operator has been in proposal for a decade at this point.. it's depressing that some version hasn't shipped, because C# extensions and similar in swift can really help shift how from what.. it* could really help JavaScript a lot
If you are interested in this kind of things, also have a look at fp-ts and it’s version of pipe and flow. Also comes with various monads to play around with!
I don’t like the syntax . But pipe operator concept is great for helping you think about your program as a series of data transformation steps . I just write my simple pipe function and use it . No need for library .
But then you have to define each method in advance, it doesn’t look like the pipeline operator at all, but more like the old jQuery.fn.method shenanigans.
const { status } = await send(capitalize(greeting) + "!")
console.log(status)
I find that easy to read, and your pipe operator harder (but still sensible!). I guess that just reflects the backgrounds we come from, people from more functional backgrounds (maybe lisp or Haskell) will find the latter example easier I guess!
Not sure if other people have mentioned this, but Ramda's `pipe` operator is left-to-right function composition which has a similar effect to this. I use it regularly, I find it very useful.
The pipe function of `fp-ts` doesn’t have direct access to methods and properties of the pipe output like Verticalize. You have to wrap them into anonymous functions. Same with promises.
Purely functional programming is a subset of functional programming (see F# for instance). I was referring to the pipe operator syntax common to many functional programming languages like Haskell, F#, OCaml, Elixir, Elm and so on...
Nobody said the first example _isn't_ functional. The point is that function composition f(g(x)) is very common in a functional paradigm and many functional languages have added a Pipeline operator as syntactic sugar for this use case.
Nobody explicitly said it wasn't functional, but the original question was "why would you do b (the 'V' shit), instead of a?" ('is this a joke?'), and the response was "well if you care about functional programming you would!" ('is functional programming a joke?'), which is heavily implying that the first example is not functional! (or 'less' functional)
That you don't recognise that is strange to me, and if after my explanation you can't understand that, I don't know what to tell you.
If you can’t understand the difference between pure (no side effects) and functional (applying and composing functions), I don’t know what to tell you.
Right - because this library tries to fake a new language syntax feature without the actual support for it, it ends up being the worst of both worlds. Even ignoring the lines-of-code explosion, the non-standard use of parens/spacing makes it incredible difficult for me to parse.
Props to the author for trying out something neat, but this is probably a bit to clever for my liking.
Author here. Thanks for your nice comment. I added a note to say it does work with autoformatters too. Actually someone in a comment below said he or she prefers the autoformatted syntax.
Oh, that's good to know! I haven't tried it and assumed an auto formatter might misalign some of it or put things on the same line, so the visual "downward arrow" appearance would be lost
Edit: I just checked the docs again and while it does look a bit "misaligned" at the start, I do think it looks more readable with the autoformatted version. Pretty cool!
This is clean JavaScript syntax in my opinion and should be what people strive for. It's perfectly readable, it's faster, it does async correctly without any unnecessary computation, can be typed and will have a normal stack trace. Piping is cool when done right, but can introduce complexity fast. Elixir is a good example where it works wonderfully.
`greetingMsg`?
`greeting`?
Naming the other one differently?
Splitting the work done with that message and the work done with this message into their own functions?
Frankly, this always sounded like a readability issue seeking a readability problem. Splitting because the names clash, because you decided to name things, because a one-liner was less than ideally readable.
Because they provide value and document the chain of events. I fear a long list of piped expressions without any idea of what they are intended to to. Would be like big regexps.
Still not hard: In that case, instead of msg use sendMsg AND/OR in the second statement use { status, msg: resultMsg }, or just result and later result.msg.
Code is read many more times than it is written. Put in the minute or two to devise good intermediate variable names, and that will pay back manyfold every time someone needs to read and understand it.
Some code. E.g. most of my code was barely read by anyone, as most of it was utilitary or mvp or niche/local enough to never see big light. And when from time to time I read other code, e.g. on github, in projects I’m occasionally interested in, there’s barely anything resembling these high class advices. FOSS with large reach - yeah. But a library that you found for a specific hardware is “just code”. And to be honest, the difference between the two is not that big. Any decent programmer navigates both almost equally fast. As someone who modified Lua internals (typical example [1]) for employer’s needs I can say that I prefer Lua style to ceremony on top of ceremony, to multiparagraph explanations of what should be obvious to a competent developer, and to hundreds of extracted functions for the sole reason of clean-ish scopes or whatever.
But still I’ve done what people advised, for many years. And know what? It never paid back. I’m sort of tired to keep that secret and nod to this obvious overgeneralization and a blanket idea. Pretty sure I’m not alone.
I think the implication is that it's difficult to read because it's read non-linearly. You first need to start in the middle at `greeting`, then work your way out to `capitalize` on the left, then to `+ "!"` on the right, then work your way out one more layer to the left with `send`.
Some kind of pipe operator can make this easier to read by arranging the operations linearly from left to right (or top to bottom as is the case with this library). Although in my opinion this library has its own readability issues.
This won't look as good when you include all the boilerplate which defines those methods, and which needs to be repeated or otherwise included for every "value" type. You don't have to worry about this when they are pure utility functions.
Which is great until Prettier, in its infinite wisdom, decides it's better to stick four of these function invocations on one line and two on the next, so you have an obnoxious mishmash of tokens!
Which is a problem of Prettier. I wish for the day that someone builds rustfmt but for Typescript, I will happily leave Prettier and StandardJS behind.
I think it could be done today with a (big) number of ESLint plugins.
Is it? I think it's an implementation of the pipe operator rather than satire of it. Javascript doesn't provide the flexibility to extend the language with pipe operators (though tools like Babel will let you use the pipe operator already).
The clunky syntax removes all the benefits of the pipe operator. The entire thing is syntactic sugar, wrapping it in confusing functions just breaks the entire concept.
Ahh OK. Yeah, I always find trying to shoehorn language semantics via functions to be kinda awkward. The best attempt I've seen is this addition of async/await to Lua, which actually looks pretty usable [1].
I think what makes a programming language easy to use is largely due to the constraints that it imposes, through carefully thought-out design decisions.
At first the constraints may be difficult to grasp. You may feel like a feature is missing, but that's part of learning a language. Once you have learned them, the constraints actually become helpful.
One thing I dislike about JS is that it's like the wild west. You have a mash-up of all different paradigms that are possible, so you get libraries like this. No, I wouldn't say this makes code more readable, though I'm a fan of the pipe in other languages. It just makes yet another way to do the same thing.
I don't mean to be rude or ranty, so with that said, it's cool that you were able to make this. Kudos for that.
> I think what makes a programming language easy to use is largely due to the constraints that it imposes, through carefully thought-out design decisions.
That's interesting. I'm pretty well convinced of the opposite. I primarily work with Ruby, which doesn't really have a whole lot of language constraints in the context of "what can I do?". Part of the language design philosophy of Ruby is to give developers "sharp knives" and let them individually avoid chopping off their own fingers. I haven't tried my self but I would expect that something similar or identical to this could be implemented in Ruby and I wouldn't consider that to be something which makes it worse. Indeed, that's one of my favorite features of the language.
The pipe function would then iterate over the array, calling methods as described.
It's not immediately clear what should happen when a method's return type is a function, but I suppose this can be handled via convention.