I recently had a go at Uncle Bob’s latest challenge. Part of the problem was, given s and n, find the smallest factor that divided n that was greater than or equal to s. In C#, you could express this as follows:
Enumerable.Range(s, n) .TakeWhile(x => x <= Math.Sqrt(n)) .Where(x => n % x == 0) .FirstOrDefault();
I think of this as results from the Range method being “piped” through the other methods. I use similar programming techniques in Powershell, where I actually use a pipe symbol. Now, it’s worth bearing in mind that this syntax only works because of extension methods. Actually, what we’ve just written compiles to
Enumerable.FirstOrDefault(Enumerable.Where(Enumerable.TakeWhile(Enumerable.Range(s, n), x => x <= Math.Sqrt(n)), x => n % x == 0));
If you’re thinking that’s not very readable, you’d be right. Now let’s write the same thing in Clojure.
(range s n)
(take-while #(<= % (Math/sqrt n)) )
(filter #(zero? (rem n %)) )
I’ve split it up onto four lines to make it anything like readable, but determining scope would be hard on a single line. Wouldn’t it be nice if there was an equivalent of extension methods in Clojure? (You’ve guessed it, there is…)
(range s n)
(take-while #(<= % (Math/sqrt n)))
(filter #(zero? (rem n %)))
Although we haven’t reduced the number of brackets, this feels a lot more readable, mostly because the scope of the brackets is much easier to see visually. For me, this feels more elegant.
So, how do extension methods and thrush operators differ? Clojure has two “thrush” operators. ->> puts the “piped” object at the end, -> at the start of the parameter list.* Extension methods in C# perform a restricted version of ->.* In particular, you’ve got to explicitly declare that the function supports the syntax. In Clojure, the caller declares that she wants the code interpreted that way.
Finally, it’s worth bearing in mind that extension methods are a compiler level technology. You can’t write your own constructs like them. In Clojure, ->> is just a macro. You can read the source on github, and it’s not even long. (It is, however, incomprehensible if you don’t grok macros.)
*Since this is Lisp we’re using here, the start of the parameter list is usually referred to as the second element of the list.
SIDEBAR: F#, on the other hand, has |>, which works the same way as ->. ->> just doesn’t make a lot of sense in context. There’s also >>, which does nearly the same thing but doesn’t require an input value. (>> & forms) would be equivalent to #(-> % forms). (It’s also pretty close to comp) Clojure may not have >>, but it would be trivially easy to add. On the other hand, it’s worth noting that the F#/ML syntax for expressing this concept is arguably more elegant. LISP isn’t always the last answer in any discussion of elegance.