Hacker News new | ask | show | jobs
by blintson 5770 days ago
Towards the end of his entry he mentioned something about readability. I believe postfix notation is superior in both read & write-ability for FP.

Take: (reduce (lambda (x y) ...) (map (lambda (x) ...) data-set))

When you actually read this what do you do? You work inside-out to understand it. You figure out what 'data-set' is, then you figure out what '(lambda (x) ...) does to it, and so on.

You (or me at least) also write the code inside-out. You start with the data, and an idea of how to transform it, and you work your way towards that transformation.

Compare to:

((data-set (lambda (x)...) map) (lambda (x y) ...) reduce)

Of course, this brings up a lot of edge-cases. Ex.: Where does 'define' fit into this? You really want define and the variable name at the beginning.

2 comments

This, in my opinion, is one of the greatest strengths that OOP has over FP: data.map(...).reduce(...). It doesn't really have anything to do with OOP, you could just as well have such a syntax for calling functions. In F# you do have the |> operator that does something like this.

This may seem superficial, but it helps readability a lot. The human mind (or mine at least) is just not well suited to unraveling nested structures.

You can get a similar effect in Clojure with the "->" operator:

http://clojure.github.com/clojure/clojure.core-api.html#cloj...;

So you can take

    (foo (bar (baz "Hi!")))
and make it

    (-> "Hi!" baz bar foo)
Whoa! The UNIX shell's pipelines!
The odd thing is that the semantics of the Unix pipeline model is more similar to lazy sequences in Haskell or Clojure.

It is common to have one function generating a lazy sequence, another taking the output of that function and generating another lazy sequence, and so on until you get your final results out at the other end. The nice thing is that at no point in time is it necessary to have the entire sequence in memory.

A Unix pipeline is similar, in that one process consumes the output of another process as it becomes available, as opposed to having to wait for the first process to complete its task before the next process in the pipeline can start.

yep. and i suppose you won't be surprised to learn its called the pipeline operator.
I'm writing from a phone and lost a longer reply to expired link. In short:

You and the OP (cf. his assumption that you read functional code inside out) seem biased towards a chronological, bottom up reading of code.

An outside-in reading of prefix code is useful to get a top-down general grasp of the structure.

Both approaches are useful and complementary.

This duality also applies when writing code. Cf. "wishful thinking" in SICP: sometimes you want to assume away auxiliar or extraneous functionality to sketch an outline of your program.

In both reading and writing, alternating approaches helps to find the kernel of the problem quickly and to build a whole understanding at your pace, so you don't get bored or stuck.

To that effect, I find prefix notation more balanced, that is, easier to read both ways.

As a practical note, if you follow the 80 column convention long().call().chains() are space hogs and awful to break up. The equivalent problem in lisp is solved with a two-space indent.

But that's just an instance of the syntactic sugar debate. My point is not about notation but how you approach the code.