Hacker News new | ask | show | jobs
by capableweb 1205 days ago
Not the first time I see the proposed pipe operator syntax but oh my god, did they have to make it so messy?

  data
    |> Object.keys
    |> console.log
when you could have done

  |> data
     Object.keys
     console.log
Or even better, don't introduce new syntax and just make it a simple function instead

  |>(data,
     Object.keys,
     console.log))
Yes yes, I know "|>" is not a legal variable/function name right now, but also, why not?!
4 comments

Because that's not how the pipe operator works in any other language.
In Clojure it does:

    (-> "Hello, World"
        string/uppercase
        (string/split #",")
        first
        string/trim)
        
`->` is a threading-first macro, and `->>` is a threading last macro. More here: https://clojure.org/guides/threading_macros
Beat me to it while I was typing! Alas, I’m on mobile and your example is a better illustration of the point.
As someone else, it does in Clojure (which is what I wrote most of the time). But even if it didnt, does it matter? If there is a better way, the way is better, no matter if it exists in other languages or not.

If all languages tried to be the same, most of them would be boring.

Of course it is, at least conceptually. See various lisps’ threading macros (which are of course functions over code; but they correspond exactly to mapping over a series of functions, each one supplying input to the next).
I totally agree, I shouldn't have been so absolute in my statement. But JS is not a lisp. The |> foo bar baz syntax doesn't jive with any other syntax in JS, but foo |> bar |> baz does.
Implementation looks dead simple for this too:

    $ = (f, x) => f(x)
    flip = f => (x, y) => f(y, x)
    |> = (x, ...fs) => fs.reduce(flip($), x)
However, |> in typescript is quite hard to type (no recursive types). The best approximation you can get is to manually insert

   |> = <A, B1>(x:A, f1:(a:A)=>B1)
   |> = <A, B1, B2>(x:A, f1:(a:A)=>B1, f2:(b1:B1)=>B2)
At that point, a simpler binary function will make more sense

   |> = flip($)
And tsc will interpret the types in the pipe more correctly. Of course, the compiler can allow functions to be called without parentheses to avoid a macro for pipe calls, which will bring our definitions to

     $ = (f, x) => f x
     flip = f => (x, y) => f y x
     |> = (x, f) => (flip $) x f
It might also help to add a simpler function composition function too, as it will greatly help reuse without requiring you to write lambdas

     $ = (f, x) => f x
     flip = f => (x, y) => f y x
     . = (f, g) => x => $ f (g x)
     |> = . flip $
It could also help to remove those pesky parentheses from lambda definitions too, maybe with simpler declarations like `f x y =` converting to `f = (x, y) =>` and enabling automatic currying:

     $ f x = f x
     flip f x y = f y x
     . f g x = $ f (g x)
     |> = . flip $
But, have you noticed that we mostly have binary functions? We could greatly improve readability by making our "modifier" functions (aka combinators or adverbs) into operators. So civet could implement a special syntax for operator definition, and then we would write for definitions like

     ($) f x = f x
     flip f x y = f y x
     (.) f g x = f $ (g x)
     (|>) = flip . $
(Oh wait, does this look like something else?) So we would be able to express `console.log(Object.keys(data))` like

    data
      |> Object.keys
      |> console.log
or equivalently

   (console.log . Object.keys) $ data
without having to special case for pipes! But more importantly, if you enjoy the Clojure method of piping, you can define

    |>> x f ...fs = f ? |>> (f x) ...fs : x
which would give you the syntax you like. And I have a feeling the types will compile just right in a language that looks like this...
I think the pipe operator makes more sense this way. It behaves like shell pipes (grep |> sort |> uniq |> cut |> echo) rather than the Clojure equivalent.

I wouldn't put every method on a new line, though.

Cool idea, we might do that!