... considerations taken while developing this libraryIt's suggested to read Vision page before reading this one. Requirements:- 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.
- Do not require any Java language changes, i.e. the user code must be able to compile with a standard JDK compiler.
- Create a library (API), not a DSL. Ensure it works with other JVM languages, such as Scala, Groovy and others.
- 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:- 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.
- 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:
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.
- 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).
- There are additional considerations related to normalization, immutability, cacheability and other properties of AST.
UsageThe library provides 3 major classes designed for usage with static import. - Query
- Expression
- Operation
QueryThe 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(...), ...). ExpressionThe 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(...)). OperationThe class provides a set of operations requiring actual enumerating the object stream, i.e. count(), first(), last(), elementAt() etc.
|
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
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).
- 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.