The Poor Man's Lambda - Revisited

Published 24 September 07 07:26 PM | andersnoras 

When I was writing the early spikes for the Quaere project, I wrote a blog post entitled "The Poor Man's Lambda", back then I used anonymous classes for the "lambda" expressions.

    Integer[] numbers={ 5, 4, 1, 3, 9, 8, 7, 2, 0}; 
Object[] firstSmallNumbers = (Object[]) from(digits).where(new Predicate() { public boolean eval(Object elm) { return ((Integer)elm) >= getContext().getIndex(); } }).select();

This changed drastically when I finally unveiled the Quaere project at JavaZone. By then I had changed the implementation to rely solely on string parsing to have lambda expressions that resembled those we are accustomed to from LINQ.

    Integer[] numbers={ 5, 4, 1, 3, 9, 8, 7, 2, 0};
    Iterable<Integer> firstSmallNumbers = 
        takeWhile("(n, index) => n >= index");

The lambdas in the JavaZone snapshot are more compact and easier to read, but still I didn't feel too good about the implementation. The syntax is a carbon copy of the C# lambda syntax and even seasoned C# developers have difficulties groking the lambda expression syntax. Java still lack closures, and C# lambdas are very unfamiliar to the average Java developer. Another problem is that the lambda expression syntax deviates from the conventions used in "regular" queries, the lambda doesn't use any of the expression factories help the user write the query. For instance the expression part of this lambda expression could be written like this using the regular conventions.

    ge("n","index")

To avoid users to learn multiple ways of writing expressions, lambda expressions have been redesigned to use fluent interfaces in Quaere. Below is the new syntax for the previous query.

    Integer[] numbers = {5, 4, 1, 3, 9, 8, 7, 2, 0};
    Iterable<Integer> firstSmallNumbers = 
        take("n").withIndexer("index").
        from(numbers).when(ge("n","index"));

The syntax is a little more verbose, but IMHO this is a good tradeoff compared with the obscurity of the string based approach. Many people have been skeptical towards the use of string expressions in Quaere, I hope more people are comfortable with the new approach to the more advanced expressions. 

Filed under: , , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Johannes Brodwall said on September 25, 2007 3:07 PM:

I am almost ashamed to admit it, but I didn't really understand your code when we talked about it over beer, and I am afraid I still don't understand it.

The core of Quaere is core to a lot of problems that I see in my code as well. But I don't understand what I'm supposed to use things like take.withIndexer for in a real application. Is this a very narrow use case, or am I being dense?

# andersnoras said on September 26, 2007 1:22 AM:

The use cases for some of the partitioning operators are quite narrow, but I still need to support these to get passing tests for all of the Microsoft LINQ 101 samples. The scenarios are available at http://msdn2.microsoft.com/en-us/vcsharp/aa336757.aspx

Still partitioning operators can be handy for query optimization. Consider the this query:

   Iterable<String> first3WAOrders =

       take(3).from(

           from("c").in(customers).

           from("o").in("c.getOrders()").

           where(eq("c.getRegion()","WA").

           select("o.getOrderID()")

       );

This query can be translated to the following SQL query.

    SELECT TOP 3

          o.orderid

    FROM

          Customers c, Orders o

    WHERE

          o.customerid = c.customerid AND

          c.region = 'WA'

Notice the "SELECT TOP 3" which is faster than selecting all of the orders...

# Carl Rosenberger said on September 26, 2007 3:15 PM:

Strings are evil.

For db4o native queries we use exactly the same approach as you used it originally:

List<Cat> cats = db.query(new Predicate<Cat>(){

   public boolean match(Cat cat){

       return cat.getFirstName().equals("Occam")

           && cat.getAge() == 1;

   }

});

Under the hood we analyse the match method using bloat and convert as much as we can to our query API to run against database indexes where possible.

Closures will solve things nicely in the future. If you can't live without closures today, consider programming in Scala (compiles to Java bytecode).

...my 2 cents.

# andersnoras said on September 27, 2007 12:30 AM:

@Carl;

I actually considered using Groovy when I first started thinking about writing a LINQ like library for the Java platform, but I decided to go for a pure Java API because many Java developers cannot use Groovy, Scala, JRuby or any of the other languages that would be more suitable because of company policies and similar. To make Quaere a viable alternative for these developers, I decided to live with all the challenges that arise from plain old Java's limited support for these features.

Another challenge is that I need to have an abstract syntax tree for the closures to be able to support any queryable resource. (Quaere builds an AST of the entire query behind the scenes.)

While this would be a breeze if I had used Groovy, it is not with Java. There are some dirty tricks that can be used to analyze the byte code at runtime, but this isn't a viable option because of performance and JVM support.

# Carl Rosenberger said on September 27, 2007 6:52 AM:

I agree with your assertion that Groovy and Scala aren't yet a choice for the majority of Java developers.

Scala as a language is really really nice. IDE support in Eclipse is only rudimentary at this time. Here is the latest development:

http://www.nabble.com/-scala--Scala-plugin-BETA-tf4473177.html

Yes, we know the issues with analyzing bytecode to generate ASTs. Performance is not an issue.

# Zac said on September 27, 2007 7:57 AM:

What about joins between different collection of objects. LINQ can do them. Quare can do that as well?

# Zac said on September 27, 2007 8:15 AM:

ok...it seems Quaere has a support for joins...i hadn't seen the example...sorry

# andersnoras said on September 27, 2007 8:22 AM:

@Zac;

Its still a very good question though. The support for joins is good as long as you join across the same Queryable<T>. Eg. joining collections with collections is efficient, but joining two different Hibernate sessions is not. There is quite a lot of work remaining with query rewriting to actually make this efficient.

# Anders Norås' Blog said on September 28, 2007 4:20 AM:

Yesterday I came across Chris Wanstrath Ambition project which is very similar to my Quaere project.

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below