Extension methods, Lambda expressions and IronPython
Together with extension methods,
lambda
expressions are the enablers of linq. IronPython (and python) support the
notion of lambda expressions. However, there is no
direct support for calling Extension methods in IronPython (yet). That is
to say, you can call them, but only in their 'static method in static class
form', like so:
customers = GetCustomers()
namesOfJohns = Enumerable.Select(\
Enumerable.Where(
customers, \
lambda c : c.Name.StartsWith("John")), \
lambda c : c.Name);
This works, obviously, but the syntax is hardly readable, you will agree.
Not convinced? What about this:
namesOrJohnsOrdered = Enumerable.Select(\
Enumerable.ThenBy(\
Enumerable.OrderBy(\
Enumerable.Where(\
customers, \
lambda c : c.Name.StartsWith("John")), \
lambda
c : c.BirthDate), \
lambda c : c.Name),
\
lambda c : c.Name);
In C#, using the Chained Method syntax, it looks like this:
var namesOfJohnsOrdered = customers
.Where(c => c.Name.StartsWith("John")
.OrderBy(c => c.BirthDate)
.ThenBy(c => c.Name)
.Select(c => c.Name);
I rest my case :-).
So, what do we want to achieve?
Well, as we saw, Python does have the notion of lambda expressions, so we’d
like to reuse those. But can we achieve the chained syntax in IronPython,
similar to the C# example above? If we could, it would look like this:
namesOfJohnsOrdered = customers.\
.Where(lambda c :
c.Name.StartsWith(“John”))\
.Select(lambda c : c.Name)
How to achieve this?
If you think about it, extension methods are in fact a fancy way to
implement the
decorator pattern.
The decorator pattern can be used to make it possible to extend (decorate)
the functionality of a certain object at runtime, independently of other
instances of the same class, provided some groundwork is done at design time.
This is achieved by designing a new decorator class that wraps the original
class.
What if we wrote a class that extends IEnumerable<T>, with wrapper
functions for the all linq operators we want to use? Here's what I have in
mind:
public class ToLinq<T> : IEnumerable<T>
{
private readonly IEnumerable<T> _wrapped;
public ToLinq(IEnumerable<T> wrapped)
{
_wrapped = wrapped;
}
public ToLinq<T> Where(Func<T, bool>
predicate)
{
return new
ToLinq<T>(_wrapped.Where(predicate));
}
public ToLinq<U> Select<U>(Func<T, U>
selector)
{
return new
ToLinq<U>(_wrapped.Select(selector));
}
public bool Any(Func<T, bool> predicate)
{
return
_wrapped.Any(predicate);
}
public int Count()
{
return _wrapped.Count();
}
// etc, you get the idea
}
We can use this from IronPython as follows:
import clr
clr.AddReference("ConsoleApplication3")
clr.AddReference("IronPythonLinqBridge")
from IronPythonLinqBridge import *
from ConsoleApplication3 import Customer
def Enumerate(customers) :
johns = ToLinq[Customer](customers)\
.Where(lambda c:
c.Name.StartsWith("John"))\
.Select(lambda c: c.Name)
for name in johns : print name
As you can see, the ToLinq class in IronPython is called like a method. This
covers the scenario where we have some .Net code that passes an IEnumerable
sequence to IronPython. It also works with standard python sequences.
This script is called from C# code like so:
var engine = Python.CreateEngine();
dynamic enumerator = engine.CreateScope();
engine.Execute(File.ReadAllText("enumerator.py"), enumerator);
var customers = new[]
{
new Customer {Name = "John Johnson"},
new Customer {Name = "Gosse Jordan"},
new Customer {Name = "John Donahue"},
new Customer {Name = "Andie Reynard"}
};
enumerator.Enumerate(customers);