To tell the truth, it's a mystery to me how exactly the speed differences come about.
The most dramatic change from 3.23 will be that the generated Java code will be fully generic, with the exception of higher rank functions, where the forall type variables are replaced by certain pseudo types.
This has a number of interesting consequences ... I'll lay that out in detail in a new "How to call Frege vom Java" later. The idea is that the whole thing should be much easier since your IDE can tell you exactly what type the function has that you are calling. It can tell you also exactly what the result type is.
For example, here is like some well known list functions look like:
final public static <α> boolean elem(PreludeBase.CEq<α> ctx$1f, Lazy<α> arg$1, PreludeBase.TList<α> arg$2) { ... }
final public static <α, β> PreludeBase.TList<α> map(final Func.U<β, α> arg$1, final PreludeBase.TList<β> arg$2) { ... }
final public static <α, β> α fold(Func.U<α, Func.U<β, α>> arg$1, α arg$2, PreludeBase.TList<β> arg$3) { ... }
You get the idea.
Now, the crucial point seems to be the implementation of the function type (higher order functions will generally appear as unary (curried) function type Func.U). Whereas up to now, every apply() created a thunk (i.e. Delayed), the functions in 3.24 are just nested lambdas, and always return the evaluated value. This, in itself, seems to save lots if intermediate thunks, and the access to the arguments from outer lambdas are maybe faster through closures.
Anyway, it's not the Java8 lambdas themselves! We do actually have 2 different implementations of Func (unfortunately, I tried to avoid it, but there is no way, since for complicated reasons functions must implement the Lazy interface with method call() from Callable. Sure, I can write the default method in the Java8 code, but then it doesn't compile in or for Java7. Likewise, I can write a plain old interface with methods apply() and call(), but then Java8 refuses to accept this as functional interface.) So I have the compiler import either frege.run7.Func or frege.run8.Func, depending on what the target is. And when the target is 1.7, then it simply pretty prints the generated lambda expressions as
new Func.U<A,B> { public B apply(Lazy<A> arg) { ...}) --as before,but with generic types
So, I ran some tests based on the same Frege code. The first one compiled with 3.23. The second one compiled with the pre-alpha 3.24 with target 1.7, and the third one with target 1.8. And surprisingly, while the old code is always substantially slower, there is no significant difference between the new java7 code and java8 code!
As always, I learned some interesting things about Java. For example, did you know that you cannot reference a final static member variable from within a lambda expression in its initializer? but with the old fashioned new Func.U() { ... } syntax, you can. (Why in the world is this so?)
BTW, it should be possible to checkout the gen8 branch and make the compiler. This will then be the old compiler plus some passes (most prominently the code generation) that do the new stuff and get activated with command line flag -experimental.
To run the future compiler, use the following flags: -prefix xx -sp next/:. -experimental
The prefix makes sure it compiles to build/xxfrege etc. and doesn't overwrite the 3.23 code.
The -sp is needed because of some necessary adaptions to library files, and makes sure the compiler uses the future versions when compiling with -make and also if you just give the module name instead of the file name. Source files that have counterparts below next/ will probably not make it through javac when compiling them with -experimental.
And finally, the -experimental flag selects the new code generator.
But please don't open any issues regarding this. There are certain problematic constructs that can't be compiled yet. Likewise, -inline or -O doesn't seem to work at all currently. It's just still "experimental".
Regards, Ingo