Hacker News new | ask | show | jobs
by honkycat 1980 days ago
Re: Pipe operator

Their argument for replacing the pipe operator is to do this instead:

foo(X) ->

    final_function(maybe_function(X)).
into this:

foo(X) ->

    Maybe = maybe_function(X),

    final_function(Maybe).
instead of this:

x

|> maybe_function()

|> final_function()

Their exact words:

Spelling things out so pedantically makes code dead-simple & clear. Yes, there is a tad more code, but you will also note that nothing is hiding. Un-nesting simply dumbs things down. Now, who wouldn’t want that after hours of squinting at a screen?

People love to trot out the "It's more explicit, its a bit more code but isn't it more READABLE" argument about everything. I've heard this argument used to oppose information hiding while refactoring functions to be smaller.

And I would argue that "readable" is subjective, and that their argument is extremely weak. I love the pipeline operator and think it should be standard in every language.

Also, his "Maybe" variable becomes a pain to maintain when you have a pipeline of multiple functions.

edit: for bad formatting

3 comments

This reminds me of the attitude held by some Go developers (and Rob Pike) that the reason people want map/filter/reduce and similar generic functions on containers is because they want "less code", and the counter is that a for loop is "more explicit", "clearer", "less magical", and so on.

But for someone who is used to languages where these constructs (or list comprehensions) are idiomatic, they are perfectly clear and explicit, and using a for loop instead adds complexity.

If you're interested in what the code is trying to achieve, then `map`, `filter` and the like is clearly more explicit.

If you're interested in what actually gets executed (if you're trying to optimize for performance, say), then the `for` loop is clearly explicit than the abstract counterparts.

It depends on what what you're looking for.

> If you're interested in what actually gets executed (if you're trying to optimize for performance, say), then the `for` loop is clearly explicit than the abstract counterparts.

In what language? GCC will happily not just optimize away your counter variable but perhaps even replace your whole loop with an SIMD operation; I suspect the Go compiler will do the same.

Also, if you're trying to optimize for performance then looking at what code gets executed is (to first order) useless; the dominant factor is cache efficiency and you can't see cache hits/misses by reading the code.

`map` can compile to wildly different things depending on what you're mapping over.

Cache efficiency is important, but so is not running O(n^2) algorithms when you didn't mean to. The latter has an outsized impact on the performance of a program, and explicit loops makes it very clear when something's up.

> explicit loops makes it very clear when something's up.

Disagree. Explicit loops hide the essence of what's happening beneath a pile of ceremony. It's easier to spot accidentally O(n^2) code in map-style code where it's a lot clearer what the code's doing.

My 2 cents: I like functional programming style but it’s annoying to have both. I like that in Golang there’s usually one way to do it, even if it’s more code. Eventually most codebases look like they’re all part of the same codebase and it’s much easier to read. In Rust people alternate between for loops and map and it really takes a while to get used to the different ways people write rust.
Yeah, I went on to read that post and also didn't quite understand their reasoning.

It seems to me that one of the benefits of the pipe operator is that it very clearly lays out the steps involved, aka

  x
  |> maybe_function()
  |> final_function()
so that the code is dead-simple and clear. It doesn't seem nested in the say way that final_function(maybe_function(X)) is.

Granted, I don't have nearly the level of experience of the author so I can't say for sure, but it seems to me like they just haven't had enough experience with the pipe operator to see how it benefits the code "beauty".

Tbh I like both the erlang style and the Elixir style and don't see a _tremendous_ amount of difference, e.g.

    foo(X) -> 
        Y = maybe_function(X),
        final_function(Y).

Both, in my eyes, yield a declarative flow without mutations and overwriting variables
The pipe operator, if you wish to use it, also has the benefit of enforcing consistency when writing functions where the primary data structure being acted on has to be the first argument.

There is, of course, still people asking for a way to specify the argument to pipe into, but I, for one, am very pleased this was never introduced (and there are no signs it ever will be).