Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Show HN: A JavaScript function that looks and behaves like a pipe operator (github.com/laurentpayot)
120 points by FroLeMageBlanc on Sept 27, 2023 | hide | past | favorite | 142 comments


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:

  pipe((p) => ([
    "hello",
    p.concat("!"),
    send,
    p.status,
    console.log
  ]));
Where `p` stands for Proxy to the previous result. Under the hood it would just return an object describing the name of the called method and arguments passed.

The pipe function would then iterate over the array, calling methods as described.

It's not immediately clear what should happen when a method's return type is a function, but I suppose this can be handled via convention.


Gulp[1] and node streams sortof do this

  gulp.src(config.src)
    .pipe(uglify())
    .pipe(gulp.dest(config.dest))
    .pipe(size());

[1] https://www.npmjs.com/package/gulp



Nice idea, but something as simple as this doesn't work:

    pipeSync(p => [
      "hello",
      p + " world",
      console.log
    ]);


Reminds me of something I tried hacking together in python for fun

  from functools import partial
  
  class Pipeable:
      def __init__(self, fn):
          self.fn = fn
  
      def __ror__(self, lhs):
          return self.fn(lhs)
  
  def pipeable(fn):
      return lambda *args: Pipeable(partial(fn, *args))
  
  filter = pipeable(filter)
  map = pipeable(map)
  list = pipeable(list)
  sum = pipeable(sum)
  min = pipeable(min)
  max = pipeable(max)
  any = pipeable(any)
  
  # Usage:
  
  range(1, 100) | filter(lambda x: x < 50) | max()
  # 49
  
  [1, 2, 3, 4] | filter(lambda x: x % 2 == 0) | map(lambda x: x * 3) | list()
  # [6, 12]
  
  [1, 2, 3, 4] | map(lambda x: x >= 5) | any()
  # False


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.


Ooh, I love this style of operator overload. Really clever.


Honestly, if prototype pollution was not a problem for optimization and other things I’d just toss a

    Object.prototype.pipeTo = function(f) {
      return f(this)
    };
And call it a day. It does about 80% of what a pipe macro would do, has instant compatibility with other prototype methods and leads to very simple types. The only issue is that you have to define lambdas to call multivariable functions. D has got this very, very right.


Nowadays, as someone writing a library, you can work around prototype pollution via Symbols and let your users modify prototypes on their terms.


Would that mean that instead of obj.pipeTo() I'd have to call obj[PIPE_TO] or something like that?


Essentially. And then have:

  Object.prototype.pipeTo = Object.prototype[PIPE_TO]
or some other name instead of `pipeTo` should there be a conflict.

JS already has a few well-known symbols like Symbol.toPrimitive that allow one to modify an object's behaviour (in this case what happens when `valueOf` or `toString` is called), so there's precedent.



I had a hard time reading the example until I applied the "normal" JS indentation, that would also likely be applied by Prettier and the likes.

  V(greeting,         // initial value  "hi"
      V(capitalize),  // custom function call  "Hi"
      V.concat("!"),  // String method `concat` call  "Hi!"
      V(send),        // custom async function call  Promise { <pending> }
      V.status,       // automatic promise chaining + getting property  Promise { 200 }
      V(console.log), // automatic promise chaining + global function call  logs 200
  )

It's certainly a nice usage of JavaScript's Proxy object and the resulting syntax is simple to grasp. Also glad to see the TS definitions in there. Well done!


Author here. Thanks for your nice comment. I added a note to show the "normal" autoformatted syntax.


Just like Clojure's threading operator.

  (-> x
     (f)
     (g)
     (h))
https://clojure.org/guides/threading_macros


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.


Proposal from 2015 to add pipes to JS:

https://github.com/tc39/proposal-pipeline-operator


Lisper screaming

See Clojure macros for thread-first `->`, thread-last `->>`, thread-as `as->`, `some->`, `some->>` and `cond->`: https://clojure.org/guides/threading_macros

You can leave all this hurt behind you and use ClojureScript, which compiles to JavaScript.


Example:

    (->> (range 10)  ;; (0 1 2 3 4 5 6 7 8 9)
      (filter even?) ;; (0 2 4 6 8)
      (map inc)      ;; (1 3 5 7 9)
      (apply +))     ;; 25
     => 25
which macroexpands to:

    (apply + (map inc (filter even? (range 10))))


This would be my preferred syntax:

    status = greeting+"!" ~> capitalize ~> send
This would need 2 changes to JS:

1: ~> being a pipe operator

2: Calling an async function from within an async function implies await

Without 2, it would look like this:

    status = greeting+"!" ~> capitalize ~> await send


There is a proposal to add the pipeline operator |> to the standard that works similar to what you have here.

https://github.com/tc39/proposal-pipeline-operator



The difference is that they want to turn

    a = d(c(b,7))
into

    a = b |> c(%,7) |> d(%)
and I would like to see it turn into

    a = b,7 ~> c ~> d


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):

    a = b |> c(7) |> d
    a = d(c(7, b))
Too bad it was withdrawn.

[1]: https://github.com/tc39/proposal-smart-pipelines

P. S. When studying Haskell on a CS course in school, I used to define exactly the same squiggly arrow operator for my programs :-)

    x ~> f = f x


> 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!

[1] https://hackage.haskell.org/package/base-4.18.1.0/docs/Data-...


This:

    a = f((e(d(c(b))), 4), 5)
Seems not to make sense. Replacing c(b) with x we get:

    a = f((e(d(x)), 4), 5)
Replacing d(x) with x we get:

    a = f((e(x), 4), 5)
Replacing e(x) with x we get:

    a = f((x, 4), 5)
What is that?


Sorry, messed up parens here. It should have been:

    a = f(e(d(c(b))), 4), 5)
b is passed to c, then the result is passed to d, then to e along with 4 as a second parameter, and finally to f along with 5.


    a = f(e(d(c(b))), 4), 5)
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


    b ~> c ~> d,4 ~> e
This reads as

    b ~> c ~> (d, 4) ~> e
to me. Sure, you can get used to it, but it’s pretty unnatural. Is this syntax used in any other languages?


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.


Aside, but there's another kind of syntax possible here:

    const result = (
      await fetch(url)
        .json()
        ::Object.keys
        .map(key => ...)
        ::new ByteArray
    );


Does JS have a splat operator? So:

  a = (b, 7) |> c(*%) |> d(%)
Too much operator soup I guess.


Yeah, it is the ellipsis:

  a = b |> c(...%, 7) |> d(%)
Spaciousness helps here a bit, but still way too many syntax, I agree.


It took me a moment, but your version is very nice.


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.


Thanks for linking Remeda! I also dislike the docs for fp-ts and that's killed adoption.


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'''


Is this just

  greeting = capitalize(greeting);
  greeting = greeting.concat("!");
  greeting = await send(greeting);
  greeting = greeting.status;
  console.log(greeting);
but with less accurate breakpoints?


yes


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.


I created something similar for Tcl called pipethread [0] [1], but using something closer to the pipe syntax.

It has a lot of interesting features that would be difficult to replicate in JavaScript.

[0] https://wiki.tcl-lang.org/page/pipethread

[1] http://www.rkeene.org/tmp/pipethread-presentation-withnotes....


  export const pipe = <A, B>(f: Fun<A, B>) => {
      return {
          to: <C>(g: Fun<B, C>) => pipe((arg: A) => g(f(arg))),
          build: () => f,
      };
  };
Much simpler alternative.

  const process = pipe(readLines).to(x => cut(x, 2)).to(....).build()

  const result = await process(fd)
P.S the Fun type is just (...args: A) => B


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


The JS proposal is very dubious imho, I'm really not fond of the direction they want to take the pipe operator.


Still prefer

    pipe([
      f, 
      g,
      h,
    ])(123);
Or, you know, a functional language.


That's not `pipe`, that's `compose` or `flow`.


That looks more like compose rather than apply (i.e pipe) to me


[ fn1, fn2, fn3, fn4 ].reduce((s, i) => i(s), starting_value);

Same thing. Less code, standard functions.


I jokingly thought to myself, when do we just move to Elixirscript?

And then, remembering that everything has already been invented, I found this:

https://github.com/elixirscript/elixirscript

You're welcome.


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!

[1] https://github.com/gcanti/fp-ts


Not sure if lodash is still in fashion but I use their flow and fp_ functions often. I like the readability, and tree shaking still works.


Its all a problem of formatting, for me this is simple to read.

  const { status } = await
    send(
      capitalize(greeting)
      + "!"
    )
But i would not concat things in a method call, bad style.

  const message = capitalize(greeting) + "!"
  const { status } = await send(message)
The example from the page is a little bit too simple.

With complex operations a pipe-method would make more sense.

But i will wait for the native pipeline-operator <https://github.com/tc39/proposal-pipeline-operator> instead using now a function.


I think the use of template strings simplifies it, even inline:

const { status } = await send(`${capitalize(greeting)}!`)

console.log(status)

It does reduce the number of discrete operations to make the pipe example look more impressive, though.


But you have to look at the whole string and check if send has a further parameter. With `send(msg)` you see it without taking extra time of reading.

And let the compiler optimize your code, that is the job of a compiler. Write your code for humans.


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 .


I use ppipe: https://github.com/egeozcan/ppipe

Looks tidier to my eyes

    const newPipe = ppipe.extend({
      divide (x, y) {
        return x / y;
      },
      log(...params) {
        console.log(...params);
        return params[params.length - 1];
      }
    });

    const res = await newPipe(10)
      .pipe(x => x + 1)
      .divide(_, 11)
      .log("here is our x: ") //logs "here is our x: 1"
      .pipe(x => x + 1) // 2


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.


That's not the case at all, have a look at the docs. The thing is pretty powerful.


This is cool, I love JS syntax hacks. Well done!

  const { status } = await send(capitalize(greeting) + "!")
  console.log(status)
I find that easy to read, and your pipe operator harder (but still sensible!). I guess that just reflects the backgrounds we come from, people from more functional backgrounds (maybe lisp or Haskell) will find the latter example easier I guess!


Author here. Thanks! Yes I have to confess I have both a Scheme and Elm background ;)


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.

https://ramdajs.com/docs/#pipe


How's it different from other `pipe` implementations around, e.g. the one from `fp-ts`?

https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe


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.


You can always

   pipe(
     2,
     double,
     x => x.toString()
   )


I love your approach too, it's really more of a pipe operator than a `pipe` function which expects the pipeline to happen on the arguments side.


90% of the time a little weed enhances coding productivity and solution clarity, the other 10% however…


This is cool and, if it interests you, you might like a thing I have been using for many years.

https://www.npmjs.com/package/qtools-asynchronous-pipe-plus


I can see this being useful but boy does it look ugly


Prepending doesn’t help its case. I like the R convention of pipe at end of line, indent on lines 2+


Interesting! I usually do exactly the opposite when I can. E. g. in Bash I would rather do this:

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
      | gpg --dearmor \
      | sudo tee /etc/apt/keyrings/docker.gpg \
      > /dev/null


I suppose it's just what you're used to, of course.

However, in R, you don't need to trailing slashes on new lines. Plus, their pipes are hideous: %>% or now |>.

I do kind of like how the pipe delimiter looks on the left.


I love it!! Innovative idea, love webdevs ranting in the comments too, how dare you threaten their precious abominations called JavaScript!?


Haha thanks! Yes haters gonna hate. If you like it, use it; if you don’t, simply don’t use it ;)


Finally, now it's easy to read code on a phone


How about just use F# and https://fable.io


bravo! loved it. I am so eagerly waiting for pipe function. your take on it meanwhile looks good to me.


Thanks! I’m currently trying to find a better TS type but that’s a bit hairy...


Just wait a few years and most dynlangs will have a pipe operator built-in.


)))))


it could have been

V(greeting, capitalize, ['concat', "!"], send, 'status', console.log )

but at that point it's already Ramda or smt and doesn't look as funky


It is Vavascript.


When in rome ...


this is all fun and games until prettier turns it into an absolute mess.


Paraphrasing: this code is unreadable so let me show you some code that is even harder to read.


>The following example code is a bit hard to read:

  >const { status } = await send(capitalize(greeting) + "!")
  >console.log(status)
No it's not hard to read.

  >Make it less nested, more vertical, by using the V "pipe":
  >V( greeting,     // initial value  "hi"
  >V (capitalize),  // custom function call  "Hi"
  >V .concat("!"),  // String method `concat` call  "Hi!"
  >V (send),        // custom async function call  Promise { <pending> }
  >V .status,       // automatic promise chaining + getting property  Promise {200}
  >V (console.log), // automatic promise chaining + global function call  logs 200 )
This cursed abomination is a joke isn't it?


Someone decided to create a solution and then started looking for problems that will fit it. Finding none, they invented one.


For some reason I was compelled to click on your profile and noted that this your first comment after 7 years of inactivity. Welcome back


Is functional programming a joke? YMMV...


I don't think the first example is less pure than the second, what makes you think it is?


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...


Ok, you aren't arguing for 'pure' functional programming, but I think you have sidestepped my question, then:

The pipe operator is just one part of functional programming, composing and applying functions

`f(g(x))` is still functional, and I would argue it exactly what is happening in the first example.


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.


English isn't your first language, so I understand your confusion throughout this thread.

It's not a problem, don't worry about it you will get there. Enjoy your day!


  The following example code is a bit hard to read:

  const { status } = await send(capitalize(greeting) + "!")
  console.log(status)
I disagree, I find this example code very easy to read because it reads like idiomatic Javascript. Unlike this library.


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.


Just an FYI to folks, there has been a proposal to add pipes to JavaScript for a few years:

https://github.com/tc39/proposal-pipeline-operator


> the non-standard use of parens/spacing makes it incredible difficult for me to parse.

Same here. Not to mention this wouldn't work in a codebase with an autoformatter.The idea/API is clever. So, a pretty cool experiment I suppose


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!


I agree with the author that it’s a little confusing, but that’s why I wouldn’t write code like that.

    const msg = capitalize(greeting) + “!”;
    const { status } = await send(msg);
    console.log(status);
Now it’s perfectly readable, no weird shenanigans required.


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.


But at what cost? You had to name two intermediate variables - and naming things is hard.


In this case the variable is the documentation of the code.

You don't have to write something like "this is the message", you can see it.


What if it isn't a message? What if "msg" is already used for another variable?


This is some wonderful performance art you’re doing here


`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.


"_msg", then.


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.


Not hard: msg can be derived from the parameter name of the send() function and status is a fixed attribute of the result.


Even worse - you’re polluting your local namespace with names decided on by other code.

I’m obviously not being serious about naming being hard in this example.

But there are cases where the pattern of using return object property names as variables comes back to bite uou.

Even in this case, what if the response object from ‘send()’ also has a ‘msg’ property?

Changing const { status } into const { status, msg } is an error.

We should be cautious about syntaxes that force us to allocate names. Sometimes.


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.

[1] https://github.com/lua/lua/blob/6baee9ef9d5657ab582c8a4b9f88...


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.


I agree. In the example case, I don't see why left-to-right or top-to-bottom would be more readable.

Sure, it is good most of the time, but operator precedence rules exist for a reason.

I don't want to read

  2.chain(x => x * 2).chain(f)
instead of

  f(2*x)
These patterns have their place for async programming, pure FP, streams and more.

They can also be a distraction.

E.g. the library introduces its own promise-unwrapping semantics and more.

I don't see the use for such a generic implementation, although I applaud the effort.


The strength of the pipeline operators comes when you have to do method chaining things like:

  const foo = capitalize(underline(reverse(exclaim(indent(value)))));
I already forgot the amount of parentheses I needed to close with while typing that.


  value.indent()
    .exclaim()
    .reverse()
    .underline()
    .capitalize()
There’s always the OOP version of method chaining!


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.


No worries your ide will help you for the parentheses.


IDEs and linters should not be a crutch methinks because not everyone uses the same IDE or text editor. Either way, it will still be awkward to read.

Better that you either avoid that pattern or at least indent it in such a way that its not so visually confusing. Or yknow use a pipe operator.


People do tend to use the same linter and LSP. That’s why they exist in the first place.


I agree, I really don't understand this library


If you read all the text, it seems like the entire repo is intended to be a satire on https://github.com/tc39/proposal-pipeline-operator.


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.


Unfortunately I’m not getting satire vibes from that blurb. I’m only more scared for JavaScript.

I love JavaScript but this pipe thing is horrific.


Tell that to Unix/FP lovers.


hahaha i mean this implementation. piping things in the shell is great


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].

[1] https://github.com/ms-jpq/lua-async-await


So what exactly does it satirize?


Which supports the age-old notion that readability is in the eyes of the reader.


It's funny that their example is much harder to read


Yeah, it's concerning when even the contrived first example is less clear in the 'solution' than it is in the alleged problem.


Agreed.


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: