The Poor Man's Lambda

Published 29 May 07 06:44 AM | andersnoras 

I've been putting some more work into my little Linq for Java demo lately to make the tests for all the examples in the Linq samples demo pass. Some of these are going to be pretty challenging, but I'm well on my way. In general I believe the DSL is highly readable, but the lambda expressions are by far as elegant has those of C# 3.0. Consider the following demo from the samples in the Orcas CTP.

public void Linq5() {
    string[] digits = { "zero", 
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine" }; var shortDigits = digits.Where((digit, index) => digit.Length < index); Console.WriteLine("Short digits:"); foreach (var d in shortDigits) { Console.WriteLine("The word {0} is shorter than its value.", d); } }

It's Java counterpart currently looks like this:

@Test
public void canUseRestrictionPredicate()
{
    String[] digits = {"zero", 
                       "one", 
                       "two", 
                       "three", 
                       "four", 
                       "five", 
                       "six", 
                       "seven", 
                       "eight", 
                       "nine"};
Object[] shortDigits = (Object[]) from(digits).where(new Predicate() { public boolean eval(Object elm) { return ((String)elm).length()<getContext().getIndex(); } }).select();


String[] expectedDigits = {"five", "six", "seven", "eight", "nine"}; for (int i = 0; i < expectedDigits.length; i++) { Assert.assertEquals(expectedDigits[i],shortDigits[i]); } }

The key difference between the two is the predicate used to restrict the selection. In the C# example this is a compact lambda expression, while in the Java version it is an anonymous inner class which implements a template method. The inner class provides access to a criteria context instance which provides things such as the element's index are made available.
If I had targeted the Java 7 runtime, I could have gotten a way with a tidier predicate using the proposed closure syntax.

from(digits).where(boolean(String elm) 
    { return elm.length() < getContext().getLength(); }
                  ).select();

I can improve the DSL by using generics to a greater extent, but still there is a long way to go before it has the full elegance of Linq. On the upside, most developers will probably find the template method approach easier to read the the "cryptic" lambda expressions of C#. To quote an anonymous reader on Sun's Java forum:

"Functions without a name?
No, functions belong to classes, and every class method has a name.
You want to use lisp!"

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

# Anders Norås' Blog said on June 12, 2007 3:29 PM:

In a recent post I showed how lambda functions are emulated in my LINQ-like Java DSL . Today I extended

# Ricky Clarkson said on June 14, 2007 1:10 PM:

I doubt it's easy to find someone who has used functions/closures extensively who thinks that the anonymous class route is a good one.

# andersnoras said on June 14, 2007 2:16 PM:

@Ricky;

I totally agree with you on that. The problem is that anonymous classes is as close as we get with Java today.

# Ricky Clarkson said on June 14, 2007 3:56 PM:

That's true, anonymous classes are as close to lambdas as we have, but you can construct the instance without using an anonymous class, too, by composing functions.  This is not a path to stray far down though, just do it when it is more convenient/more readable/less repetitive than the anonymous class equivalent:

from(digits).where(lessThan(length,getContext().getLength()));

..that is, I'm building the lambda that I want from existing functions (some of which will be defined as anonymous classes, but at the point of use there will often be more readability).

A more natural example:

if (collection.contains(equalsIgnoreCase("Hello World"))

   whatever("it does");

else

   whatever("it doesn't");

..where equalsIgnoreCase is (using [] for generics, as there's no preview button):

public static Predicate[String] equalsIgnoreCase(final String string)

{

   return new Predicate[String]()

   {

       public boolean invoke(String data)

       {

           return string.equalsIgnoreCase(data);

       }

   };

}

# andersnoras said on June 14, 2007 4:07 PM:

@Ricky;

This is very close to what I generally do in this DSL. Take a look at this post: http://andersnoras.com/blogs/anoras/archive/2007/03/23/guess-what-i-m-up-to.aspx

Which shows a sample which is very close to your example. The anonymous types are only used to allow a user to define her own projections or predicates that can be processed by the querying engine.

Anyway, thanks for the feedback. I'll try out some different designs for the custom functions, maybe along the lines of your example, to see if I can make the DSL even more readable. After all, readability is my key motivation for writing it.

# Anders Norås' Blog said on September 25, 2007 4:26 AM:

When I was writing the early spikes for the Quaere project, I wrote a blog post entitled "The Poor Man's

# Michael Hunger said on December 1, 2007 5:49 PM:

what about this, just valid jdk5 syntax:

public class Closure<T,R> {

   protected final T[] values;

   protected final T value;

   protected R result;

   public Closure(final T...values) {

       this.values = values;

       this.value = values.length > 0 ? values[0] : null;

   }

}

public class ClosureTest extends TestCase {

   public void testClosure() {

       final int input=10;

       final int result = new Closure<Integer, Integer>(input) {{

               result = value * value;

       }}.result;

       assertEquals(100, result);

   }

}

# Ricky Clarkson said on December 2, 2007 2:28 AM:

Michael, that's not a closure, in that it's not some code you can run at a later point, or many times.

For example, if the initialisation block did System.out.println("Hi") and .result was never evaluated, printing "Hi" would be wrong if this was a closure, which it isn't.

Leave a Comment

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