Ambition, Quaere, Closures and Other Random Thoughts
Yesterday I came across Chris Wanstrath Ambition project which is very similar to my Quaere project. To exemplify the similarities between LINQ, Ambition and Quaere, here is the same query expressed in each "language".
LINQ
var expensiveProducts =
from p in products
where p.UnitsInStock > 0 && p.UnitPrice > 3.0
select p; // ...and it can also be written this way...
var expensiveProducts =
p.Select(p => p.UnitsInStock > 0 && p.UnitPrice > 3.0);
Ambition
expensiveProducts = Product.select { |p|
p.unitsInStock > 0 && p.unitPrice > 3.0
}
Quaere
Iterable<Product> expensiveProducts =
from("p").in(products).
where(gt("p.getUnitsInStock()",0).and(gt("p.getUnitPrice()",3.0))).
select("p");
Quaere is the most verbose of these, and Ambition is very similar to using LINQs "Select" extension method. Ambition uses closures to define the predicates, and this is kind of similar to Groovy's findAll operation;
products.findAll { it.getUnitsInStock() > 0 && it.getUnitPrice() > 3.0}
In The Poor Man's Lambda - Revisited I explained how I moved from using anonymous classes, to expressions to define predicates in Quaere. Apart from the readability aspect, I also need to get the abstract syntax tree for the predicate so that I make it part of the expression tree used to describe the query. Ambition uses ParseTree to get the AST of the closure. Thomas Mueller and Carl Rosenberger have come up with some interesting ideas on how we can improve Quaere's type safety. Thomas has written a small API which uses an Alias class rather than the current string based variable references used in Quaere, if we implement this the above query will turn into something like this:
Alias<Product> p=new Alias<Product>(products);
Iterable<Product> expensiveProducts =
from(p).in(products).
where(p.invoke("getUnitsInStock").gt(0).and(p.invoke("getUnitPrice").gt(3.0))).
select(p);
A further option is to switch back to anonymous classes and use these to write predicates. We could probably also use a proxy to inject the necessary metadata into the alias while keeping the alias type safe.
final Product p=Alias.define(Product.class);
Iterable<Product> expensiveProducts =
from(p).in(products).
where(new Function() {
public boolean fn() {
return p.getUnitsInStock() > 0 && p.getUnitPrice() > 3.0;
}
}).
select(p);
We could probably use an ASM MethodVisitor to retrieve the AST for the fn() method and convert it into an expression tree fragment (shown below).
Still I'm not comfortable with all the noise added to the query expression by using an anonymous class because it makes the whole thing look rather arcane. So until we get closures in Java, I don't see this as a good option. But when we do, we could write something like this:
Iterable<Product> expensiveProducts =
from(Product.class).in(products).
where(
{Product p => p.getUnitsInStock() > 0 && p.getUnitPrice() > 4.0 }).
select();
We'd still need to get the AST, if we're really lucky maybe this will be as easy as it is in Groovy, otherwise ASM is probably the way to go.