Hacker News new | ask | show | jobs
by brundolf 1992 days ago
The hard part with this is it sort of requires currying once you have >1 arguments, or something equivalent. I suppose Python could carve out an implicit behavior where the first or last argument is what gets fed into, but that feels potentially confusing as the calling syntax is now "lying" to you. In JavaScript doing a proper currying style isn't too hard because of arrow-syntax, but using python's function definition syntax to make a curried function would be hideous (not to mention, the standard library isn't done that way). Maybe you could have a "curryify" higher-order function. Or, the final option would be to have an explicit "insert previous value here" syntax as a part of the pipeline syntax, which is something the JS proposal has played with. Makes things more verbose (|> double(#) instead of |> double), but is maximally flexible and minimally confusing.

In short: it's a lot more complicated than it seems, but I agree that this style makes this type of thing 1000x more readable.

2 comments

Python does have a "partial" function which does currying:

  from functools import partial

  a |> partial(zip, b) |> partial(map, func1) |> partial(filter, func2) |> partial(forall, func3)
Obviously it's a bit more verbose than if the currying was done implicitly, but it's not too bad, I think. You could also import partial under a shorter name if you want.

partial does have an advantage over implicit currying in that you can use keyword arguments to neatly curry on a parameter other than the first, although this isn't properly utilized by Python because most of the built-in functions have place-based rather than keyword arguments. In languages with implicit currying you have to use anonymous function expressions or functions like flip (flip(f, x, y) = f(y, x)) to deal with this.

It might also be worth noting that |> doesn't essentially need to be an operator, it would just be syntactic sugar:

  def chain(x, *fs):
      y = x
      for f in fs:
          y = f(x)
      return y

  chain(a, partial(zip, b), partial(map, func1), partial(filter, func2), partial(forall, func3))
Obviously having it as an infix operator is nicer, and produces less parentheses.
Just letting it implicitly be the first parameter would be good enough IMO, and a nice symmetry to self` in methods. That'd be very simple, which would be a plus in my book.

Pandas allows the first param in a pipe to be a tuple[callable, str], where the second argument would signify the parameter location, e.g. `val |> (func, "param_name")` which gives some flexibility.

But yeah, if you open up to piping, there are a lot of possible choices to be made and easy to go overboard also IMO.