Tuesday, April 22, 2014

Left to right, top to bottom

TL;DR - Clojure's threading macro keeps code in a legible order, and it's more extensible than methods.

When we create methods in classes, we like that we're grouping operations with related data. It's a useful organizational scheme. There's another reason to like methods: they put the code in an order that's easy to read. In the old days it might read top-to-bottom, with subject and then verb and then the objects of the verb:


With a fluent interface that supports immutability, methods still give us a pleasing left-to-right ordering:
Methods look great, but it's hard to add new ones. Maybe I sometimes want to add functionality for returns, or print a gift receipt. With functions, there is no limit to this. The secret is: methods are the same thing as functions, except with an extra secret parameter called this
For example, consider JavaScript. (full gist) A method there can be any old function, and it can use properties of this.
var completeSale = function(num) { console.log("Sale " + num + ": selling " 
+ this.items + " to " + this.customer); }

Give that value to an object property, and poof, the property is a method:

var sale = {
customer: "Fred",
items: ["carrot","eggs"],
complete: completeSale
}; sale.complete(99); // Sale 99: selling carrot,eggs to Fred

Or, call the function directly, and the first argument plays the role of "this":

completeSale.call(sale, 100)
// Sale 100: selling carrot,eggs to Fred

In Scala we can create methods or functions for any operation, and still organize them right along with the data. I can choose between a method in the class:

class Sale(...) {
   def complete(num: Int) {...}
}

or a function in the companion object:

object Sale {
   def complete(sale: Sale, num: Int) {...}
}

Here, the function in the companion object can even access private members of the class[1]. The latter style is more functional. I like writing functions instead of methods because (1) all input is explicit and (2) I can add more functions as needed, and only as needed, and without jumbling up the two styles. When I write functions about data, instead of attaching functions to data, I can import the functions I need and no more. Methods are always on a class, whether I like it or not.

There's a serious disadvantage to the function-with-explicit-parameter choice, though. Instead of a nice left-to-right reading style, we get:


It's all inside-out-looking! What happens first is in the middle, and the objects are separated from the verbs they serve. Blech! It sucks that function application reads inside-out, right-to-left. The code is hard to follow.

We want the output of addCustomer to go to addItems, and the output of addItems to go to complete. Can I do this in a readable order? I don't want to stuff all my functions into the class as methods.
In Scala, I wind up with this:

Here it reads top-down, and the arguments aren't spread out all over the place. But I still have to draw lines, mentally, between what goes where. And sometimes I screw it up.

Clojure has the ideal solution. It's called the threading macro. It has a terrible name, because there's no relation to threads, nothing asynchronous. Instead, it's about cramming the output of one function into the first argument of the next. If addCustomer, addItems, and complete are all functions which take a sale as the first parameter, the threading macro says, "Start with this. Cram it into first argument of the function call, and take that result and cram it into the first argument of the next function call, and so on." The result of the last operation comes out. (full gist
\\ Sale 99 : selling [carrot eggs] to Fred
This has a clear top-down ordering to it. It's subject, verb, object. It's a great substitute for methods. It's kinda like stitching the data in where it belongs, weaving the operations together. Maybe that's why it's called the threading macro. (I would have called it cramming instead.)

Clojure's prefix notation has a reputation for being harder to read, but this changes that. The threading macro pulls the subject out of the first function argument and puts it at the top, at the beginning of the sentence. I wish Scala had this!

-----------------
Encore:
In case you're still interested, here's a second example: list processing.

Methods in Scala look nice:



but they're not extensible. If these were functions I'd have:


which is hideous. So I wind up with:
That is easy to mess up; I have to get the intermediate variables right.
In Haskell it's function composition:
That reads backwards, right-to-left, but it does keep the objects with the verbs.

Notice that in Haskell the map, filter, reduce functions take the data as their last parameter.[2] This is also the case in Clojure, so we can use the last-parameter threading macro. It has the cramming effect of shoving the previous result into the last parameter:

Once again, Clojure gives us a top-down, subject-verb-object form. See? the Lisp is perfectly readable, once you know which paths to twist your brain down.


Update: As @ppog_penguin reminded me, F# has the best syntax of all. Its pipe operator acts a lot like the Unix pipe, and sends data into the last parameter.
F# is my favorite!

------------
[1] technical detail: the companion object can't see members that are private[this]
[2] technical detail: all functions in Haskell take one parameter; applying map to a predicate returns a function of one parameter that expects the list.

10 comments:

  1. Thanks, this was great! I never understood the difference between -> and ->> before. But please don't use orphan trailing parentheses, it hurts the eyes ;)

    ReplyDelete
  2. Nice post! It's helpful when learning Clojure to know that collection functions (ones that modify a collection and return an updated version like conj, get, and assoc) always take a collection as the first arg and so are compatible to ->. Sequence functions (ones that take a seqable object and return a sequence, like map, filter, and reduce) always take the sequence as the last arg and so are compatible with ->>.

    There are also some other threading macros that do more fun things: as-> (for introducing named intermediate values), some->/some->> (short-circuit on nil), and cond->/cond->> (which threads through conditional tests).

    ReplyDelete
  3. Great fit for event sourcing. An example in F# http://gorodinski.com/blog/2013/02/17/domain-driven-design-with-fsharp-and-eventstore/

    ReplyDelete
  4. Really appreciate the multi language examples, made it very easy to understand what you were saying. Great article.

    ReplyDelete
  5. Is there really now way this is doable in Scala, even if we employ macro or implicit magic?

    ReplyDelete
    Replies
    1. yeah, well, at least with implicits there is a way. See my comment below (Not sure if that's the best way to do it, though)

      Delete
  6. Enjoying these posts which compare and contrast different methods!

    There are also left-to-right as well as right-to-left composition operators in Control.Category, part of the base package in haskell. One could do something like this for right-to-left readability (or put them on separate lines):

    import Control.Arrow ((>>>))
    infixr 0 &
    (&) = flip ($)

    stream & filter predicate >>> map transform >>> reduce accumulator

    ReplyDelete
  7. 1. The dot syntax of methods is incidental, and not essential. There are languages which do not use this syntax. e.g. Factor, Common Lisp.
    2. The Clojure threading macros are well, a syntax. (A user defined syntax, but still a syntax.) Which means they'll suffer from all sorts of problems associated with it. Composition goes for a toss. Compare this with the functional approach taken in typed FP languages - The pipe (|>) in F#. (Which is also available in Scala via Scalaz.) So much elegance! I wrote a post about this before - http://missingfaktor.blogspot.in/2014/03/dead-simple-threading-with-functions-in.html.

    So the real conclusion we should be drawing is, functions > syntax. :-)

    ReplyDelete
  8. I've taken to defining and using this in Haskell recently:

    infixl 0 (|>)
    x |> f = f x

    Notice: no macros needed!

    It's also interesting how expressions using |> mirror those using >>= -- the structure/pipelining idea is similar yet the latter has extra stuff going on. In other words >>= is kind of a generalisation of |>

    ReplyDelete
  9. Hmmm but you could do something similar to F# in Scala using implicits and having your functions curried (caveat: they would be curried with respect to the last parameter, which I guess is somewhat unusual):

    implicit class Pipeable[A](val v:A) {
    def |>[B](f:A=>B): Pipeable[B] = new Pipeable(f(v))
    }
    def filter[A](p: A=>Boolean)(l: List[A]): List[A] = ...
    def map[A,B](p: A=>B)(l: List[A]): List[B] = ...

    implicit def fromPipeable[A](pip: Pipeable[A]): A = pip.v
    val list: List[Int] = ...
    val newList: List[Int] = list |> filter(predicate) |> map(transform)

    I have the feeling this is a well known abstraction. A functor?

    ReplyDelete