The Thrush combinator in C#
Posted by Jonas Elfström Tue, 06 Oct 2009 18:35:00 GMT
Last year I read Reg "Raganwald'" Braithwaite's excellent post The Thrush and he explains it as
The thrush is written Txy = yx. It reverses evaluation.
Back then I didn't even consider trying to implement it in C#. That was before I digged deeper into lambda expressions and extension methods in C# 3.0 and way before last night when I read Debasish Ghosh's post on how to implement the Thrush in Scala. After reading that my first thought was if it was possible to do the same in C#. Here's my attempt.
At first I struggled with the static typing and headed for an easy way out using Object in the extension method of Object:
1 2 3 |
public static object Into(this Object obj, Func<object, object> f) { return f.Invoke(obj); } |
My goal was to translate the Ruby example
(1..100).select(&:odd?).inject(&:+).into { |x| x * x } |
in Raganwald's post to C#.
Which reads "Take the numbers from 1 to 100, keep the odd ones, take the sum of those, and then answer the square of that number."
But with the Object based extension method I had to do some ugly casts.
var r = Enumerable.Range(1, 100).Where(x => Odd(x)).Sum().Into(x => (int)x * (int)x); |
With som added typing I could do:
var result = Enumerable.Range(1, 100).Where(x => Odd(x)).Sum().Into(x => x * x); |
That merely moved the cast to the extension method and also made it work for integers only.
1 2 |
public static int Into(this Object obj, Func<int, int> f) { return f.Invoke((int)obj); } |
Then I remembered generics and method type inference which finally led to a decent Thrush combinator in C#.
1 2 |
public static T Into<T>(this T obj, Func<T, T> f) { return f(obj); } |
The casts are gone and it's also, as far as I can see, as flexible as the one in Ruby.
Contrived example follows:
1 2 |
var test = "ball"; var ball = test.Into(s => "Are we having a " + s + " yet?"); |
The odd part
The Odd(x) method call in the calculation above is a plain static method.
1 2 |
private static bool Odd(int n) { return (n % 2 != 0); } |
If you want an even more terse syntax you could try an ext. method on IEnumerable like this:
1 2 |
public static IEnumerable<int> Odd(this IEnumerable<int> en) { return en.Where(n => n % 2 != 0); } |
Gives:
var result = Enumerable.Range(1, 100).Odd().Sum().Into(x => x * x); |
In C# I don't think it's possible to pull off the Symbol#to_proc stuff that Ruby does. That's the &: in the select(&:odd?) and the inject(&:+) in the Ruby example. Raganwald has a great post on that.
Edit
Check out Jon Skeet's nice answer on StackOverflow to my question on how to make this even more Ruby-like. I have to try out that Operator class later though.
Edit 2009-10-07
One thing I found a bit surprising is that by implementing the Into ext. method in this way it not only works for all objects based on System.Object
but it also works for value types.
1 2 3 4 |
int n=4711; int oddOrZero = n.Into(x => x % 2 !=0 ? x : 0); // 4711 n = 4712; oddOrZero = n.Into(x => x % 2 != 0 ? x : 0); // 0 |
Edit 2009-10-12
My confusion did stem from my lack of understanding of extension methods. Ex. methods are in fact not extending System.Object
or any other type, they are "nothing more than a pleasant syntax for calling a static method" in case no instance method with the same name can be found.
Great post!