My favorites | Sign in
Project Logo
                
Search
for
Updated Dec 03, 2008 by kostat
Labels: Phase-Design, Featured
Design  

... considerations taken while developing this library

It's suggested to read Vision page before reading this one.

Requirements:

  1. Provide the same capabilities MS LINQ does (type-safety, imperative API, declarative API). In particular, have an imperative API for working on in-memory object models, based on Iterable and declarative API, out of which an AST can be built. Those logically map to Enumerable and Queryable MS LINQ classes. All the targeting/rewriting capabilities are implied accordingly.
  2. Do not require any Java language changes, i.e. the user code must be able to compile with a standard JDK compiler.
  3. Create a library (API), not a DSL. Ensure it works with other JVM languages, such as Scala, Groovy and others.
  4. Design the API in a way no special syntax sugar is absolutely required. Here I made an exception and allowed closures (lambda expressions). The reason for this exception is technical: closure - interface with one predefined method, gives clear boundaries for bytecode analysis (more on this in Design section). I hope that some closure proposal will be included into Java 7, otherwise there will be no choice but working with anonymous classes, what is somewhat less elegant.

Implementation Design:

  1. Make an imperative API close to MS LINQ. This fulfills requirement (1) and provides an easy way to obtain tests, since the LINQ C# examples work after very straightforward adaptation. The additional bonus is the fact that I can use mono implementation as a reference.
  2. The declarative capabilities are provided using the following technique: given I'm able to build an AST out of a lambda expression (by means of bytecode analysis); each declarative API should return a lambda expression containing the desired implementation. The exception is 'from' method, which gets the lambda and parses it obtaining the desired AST. In a more formal way:
  3. Let's define:
    • Imperative::* : methods in imperative space.
    • Declarative::* : methods in declarative space.
    • E: expression.
    • L: lambda.
    • P: AST building function.
    Given:
    • P(L(E)) = AST(E)
    • Declarative::* = L(Imperative::*) //each method in Declarative::* returns a corresponding L(Imperative::*)
    Yields:
        Declarative::from(source, Declarative::* selector) {
            P(selector) = P(L(Imperative::*)) = AST(Imperative::*) //desired AST
        }
    See inside Query1.java:
        //simplified for clarity
        public static <T, R> Iterable<R> from(Queryable<T> source, Function<Iterable<R>, Iterable<? extends T>> selector) {
            return source.invoke((LambdaExpression<Function<Iterable<R>, Iterable<? extends T>>>)
                LambdaExpression.parse(selector));
        }
    The resulting syntax can be seen here.
  4. Make the AST to be able to express any single statement. Assignments are disallowed. This is done for simplicity and because it does not violate requirement (1).
  5. There are additional considerations related to normalization, immutability, cacheability and other properties of AST.

Usage

The library provides 3 major classes designed for usage with static import.

  1. Query
  2. Expression
  3. Operation

Query

The class provides a comprehensive set of methods receiving and returning object streams. Each method applies a business rule in a specific way and returns a new object stream for further processing.

A very useful application is feeding one function with output of another function, i.e. select(where(...), ...).

Expression

The class provides a standard set of business rules to be applied with Query class. The rules are immutable and thus can be instantiated once and used everywhere. The rules are designed to be combined to create more sophisticated ones, i.e. or(greaterThan(...), lessThan(...)).

Operation

The class provides a set of operations requiring actual enumerating the object stream, i.e. count(), first(), last(), elementAt() etc.


Comment by alex.e.frank, Jul 11, 2009

Hello kostat/JaQue? Team,

I have a couple of questions.

1. It is stated that JaQue? is not a DSL, but rather an API. It seems to me that it is a DSL? 2. I still cannot understand how LINQ queries are translated. If you have a simple query like: from emp in Employees select emp. What happens in the background? Are you using a LINQ metamodel? EMF/Metamodelling? 3. JaQue? to JPA, I simply understand it as a replacement for JPQL but functional using the IDE compile time checks and not embedding queries as strings. How is type-safety guaranteed? How is JaQue?/JPA connected to the abovementioned LINQ interpreting procedure?

Thanks for the answers. Alex

Comment by kostat, Sep 28, 2009

Missed the comment, but better later than never...


In a simplistic view JaQue? provides an implementation for Enumerable and Queryable MS LINQ classes. This implies it's an API.
It's not a DSL because its API (like MS LINQ) is not bound to any particular problem domain, rather it has a mechanism that lets translate its abstract query into domain specific representation (Queryable interface with Expression trees).
  1. I still cannot understand how LINQ queries are translated: Well, let's see how MS LINQ does it first. The declarative construct, such as from emp in Employees select emp is first translated into corresponding imperative construct, Employees.Select(emp => emp), which is translated into expression tree (if needed), fed to the appropriate provider etc.
JaQue? starts from imperative construct, which is written in Java or Scala and compiled into JBC. Out of JBC JaQue? builds an expression tree and the rest is exactly as in MS LINQ, through Queryable IQueryable? interface implementation.
  1. Are you using a LINQ metamodel? EMF/Metamodelling?: There is no need, the information embedded into JBC is sufficient.
  2. JaQue? to JPA, I simply understand it as a replacement for JPQL but functional using the IDE compile time checks and not embedding queries as strings.: No. JaQue? is not based on IDE checks, only on native compiler. Exactly like LINQ translates function calls into corresponding DSL constructs (e.g. Where() into SQL WHERE), JaQue? provides same functionality. To mention this is one of the imperative LINQ/JaQue? cabilities.
  3. How is type-safety guaranteed? By the compiler (as in LINQ).
  4. How is JaQue??/JPA connected to the abovementioned LINQ interpreting procedure? By the Queryable provider implementation.


Sign in to add a comment
Hosted by Google Code