Readit News logoReadit News
Tade0 · 2 years ago
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.

pineconewarrior · 2 years ago
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

Tade0 · 2 years ago
xigoi · 2 years ago
Nice idea, but something as simple as this doesn't work:

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

Deleted Comment

izoow · 2 years ago
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

vollcheck · 2 years ago
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.
rolandog · 2 years ago
Ooh, I love this style of operator overload. Really clever.
_v7gu · 2 years ago
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.

Tade0 · 2 years ago
Nowadays, as someone writing a library, you can work around prototype pollution via Symbols and let your users modify prototypes on their terms.
Drakim · 2 years ago
Would that mean that instead of obj.pipeTo() I'd have to call obj[PIPE_TO] or something like that?
mlajtos · 2 years ago
stby · 2 years ago
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!

FroLeMageBlanc · 2 years ago
Author here. Thanks for your nice comment. I added a note to show the "normal" autoformatted syntax.
matheusmoreira · 2 years ago
Just like Clojure's threading operator.

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

snorremd · 2 years ago
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.

newtom · 2 years ago
Proposal from 2015 to add pipes to JS:

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

pgt · 2 years ago
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.

pgt · 2 years ago
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))))

mg · 2 years ago
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

jmisavage · 2 years ago
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

madeofpalk · 2 years ago
mg · 2 years ago
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