Skip to content

Calling Frege from Java (from release 3.24 on)

Ingo Wechsung edited this page Mar 20, 2016 · 14 revisions

This guide explains how to call code generated by the Frege compiler from Java. It is valid for releases since 3.24.

Short summary of the most important changes compared to 3.23 releases

  • generated Java code is now fully generic, including higher kinded types.
  • code for Java8 and above can be generated with lambdas instead of anonymous functions.
  • certain run time classes have been dropped (Algebraic, Applicable) or replaced (Delayed, Lambda) or they are generic now (Lazy).

Frege Run Time System

The Frege Run Time System consists of a handful of interfaces and abstract classes, which are used to implement laziness, function types, and higher kinded types, as explained below. In addition, there are some provisions for IO, parallelism through the Fork-Join framework, and concurrency with thread pools. However, none of this requires any initialization, it'll just be there when the Frege code needs it.

The Java7/8 Compatibility Problem

Some of the most important runtime types, like functions, lazy values, and thunks exist twice in different packages frege.run7 and frege.run8. The reason is that there is no way to share them between code that uses Java lambdas and code that is compatible with Java7.

This is, for the time being, a dilemma for the author of Java code that calls into Frege. The following solutions are conceivable:

  • Write your Java code inline in Frege. Pro: the Frege compiler will supply the appropriate imports, and the code will run in any Java version. Con: inline code must be written without help from an IDE, and this may become hard. (Workaround: write your code first in a separate Java class, then copy relevant portions to the Frege source)

  • Be future oriented: just forget Java7. Pro: it's the future. Con: Unfortunately, due to long-standing unfixed bugs, javac8 is currently unable to reliably compile Frege generated code (while javac9 compiles the same code without complaints), so this strategy will win only when Java9 becomes the standard.

  • Make a master version for the JVM level you want to support most, but in such a way that you can derive the other version by simply replacing the relevant imports, changing frege.run7 to frege.run8 or vice versa. Pro: you can support all Frege users. Con: higher order functions will be difficult, and if you need them, you must use java7 compatible anonymous classes.

In the following, I write frege.run? when I mean: frege.run7 or frege.run8 depending on your compilation target.

Lazy values: frege.run?.Lazy

The type for modelling lazy values is basically (I show here the Java8 variant):

@FunctionalInterface
public interface Lazy<R> extends Callable<R> {
    /**
     * <p> Compute the value if it is needed. </p>
     * 
     * @see java.util.concurrent.Callable#call()
     */
    public R call();
}

From this, it should be clear, that when you have Lazy<X> and want an X, you need to invoke the call() method.

But quite as often, you have an X , but want a Lazy<X> , for example when you want to pass a value to a Frege function that expects a Lazy<X>. In this case, it is important to know that the Java incarnations of Frege algebraic data types as well as Frege function types implement Lazy (their call() method returns just this), so they can simply be passed where their Lazy form is needed.

The only exception are enumeration types that are mapped to java type short and type renamings of native types, since natives types do, of course, not normally implement Lazy.

So, how do we make a lazy value? This is the time to introduce Boxes and Thunks.

Thunks and Boxes frege.run?.Thunk frege.run?.Box

While Lazy is just an interface that doesn't specify how call() is implemented, Thunk is a class that implements Lazy and guarantees that the evaluation cost of call() (if any) is incurred at most once, because the result gets cached, waiting for the next call() where the previously computed value is just returned again. In addition, it is guaranteed that this remains true even in the presence of multiple threads, who try to evaluate that value. Only one thread will evaluate the value, which is afterwards available to all threads.

Consider the following example:

(Lazy<Integer>) (() -> /* some expensive computation */)

One can pass this to a function or data constructor that expects a Lazy<Integer> as argument. But every time the function invokes call() on that argument, the expensive computation will run. We can do better by creating a Thunk thus:

Thunk.<Integer>shared( () -> /* some expensive computation */ )

Now, the Thunk evaluation mechanism ensures that /* some expensive computation */ is performed at most once, and the result is shared among all interested parties.

Clearly, Thunk.shared is overkill if we already have a value. We would not want to write

Thunk.<Integer>shared( () -> 42 )

just to place an int in a list or tuple! Instead, the lazy method comes in handy here:

Thunk.<Integer>lazy( 42 )

This will actually create an instance of class Box, another instantiation of Lazy that is specialized on just boxing values.

Take home so far

  • The java type of a lazy value is Lazy<T> if T is the java type of the value.

  • To get the value out of a Lazy, invoke call() on it.

  • Algebraic data types and function types implement Lazy

  • Enumeration types, native types and renamings (newtype) of native/enumeration types do not implement Lazy

  • To get a Lazy<R> of a value with type R, pass the value to Thunk.<R>lazy. This is needed only if R is or could be a type that doesn't implement Lazy.

  • To delay a computation, use

      (Lazy<T>)( () -> /* compute a T */ )
    
  • To create a delayed computation that is evaluated at most once (a thunk), use

      Thunk.<T>shared( () -> /* compute a T */ )
    
  • Don't assume anything about the actual class of a Lazy value! Stick with the Lazy interface and the Thunk.shared and Thunk.lazy operations, and apart from the latter two, forget that Thunk and Box exist.

Where Frege uses Lazy

  • Fields of algebraic datatypes are lazy, unless they are marked strict with a bang. For example, list and tuple elements are lazy.
  • Higher order functions take lazy arguments and return a lazy result.
  • Class operations invoked through an instance dictionary take lazy arguments.
  • Top level functions are compiled to static methods. Their arguments and the return types can be lazy or strict, depending on the judgement of the strictness analyzer. Your IDE can help you identify the actual types.

Functional Values

Functional values appear as arguments in function calls, return values or components in data structures. They are curried and represented as lambda expressions implementing the functional interface Func.U (or anonymous classes that extend Func.U for Java7).

public class Func  {
  @FunctionalInterface public interface U<𝓐, 𝓑> 
        extends Lazy<Func.U<𝓐, 𝓑>>, 
                Kind.U<Func.U<𝓐, ?>, 𝓑>, Kind.B<Func.U<?, ?>, 𝓐, 𝓑>
   {
        public Lazy<𝓑> apply(final Lazy<𝓐> a) ;
        public default Func.U<𝓐, 𝓑> call() { return this; }
   }
  @SuppressWarnings("unchecked") final public static <𝓐, 𝓑> Func.U<𝓐, 𝓑> coerceU(
       final Kind.U<Func.U<𝓐, ?>, 𝓑> it) {
           return (Func.U<𝓐, 𝓑>)it;
  }
  @SuppressWarnings("unchecked") final public static <𝓐, 𝓑> Func.U<𝓐, 𝓑> coerceU(
       final Kind.B<Func.U<?, ?>, 𝓐, 𝓑> it) {
           return (Func.U<𝓐, 𝓑>)it;
  }
}

As can be seen from the signature of method apply, one can apply a lazy value and gets another lazy value back. The return value can also be another (lazy) function that takes further arguments.

Partial application

Partial application is easily attained by constructing a Func.U that picks up the remaining argument and in the innermost lambda body just calls the appropriate static method. Here is the code that corresponds to

foo xs = map (1+) xs

final public static PreludeBase.TList<Integer> foo(final PreludeBase.TList<Integer> arg$1) {
  return PreludeList.<Integer, Integer>map(
        (Func.U<Integer, Integer>)((final Lazy<Integer> η$19697) -> Thunk.<Integer>lazy(
                   1 + (int)η$19697.call())
                ),
        arg$1
      );
}

If you need to pass a function, treat it like a partial application without fixed arguments. That is, nest as many Func.U as the function has arguments, and in the innermost body, call the function with all collected arguments. See also the example below.

Converting between Java8 lambdas and Frege functions

Frege doesn't make use of the functional interfaces provided by Java8 and higher in java.util.function for a variety of reasons:

  • We need backwards compatibility with Java7.
  • The functional interfaces have no notion of laziness in arguments and return types. This makes it difficult to enforce correct use through the java type system.
  • The functional interfaces don't implement Lazy themselves.
  • Most functional interfaces are just too specialized to be useful in Frege.

However, conversion between Frege functions and those interfaces should be particularly easy. As a showcase we use java.util.function.BiFunction

(The following examples are not in the standard library, due to Java7 backwards compatibility issues. However, we will provide functionality in a source file later this year.)

-- make the interface type known to Frege
-- we deal only with pure functions here!
data BiFunction t u r = pure native java.util.function.BiFunction {t,u,r} where
    pure native apply :: BiFunction t u r -> t -> u -> r
    pure native from  frege.run8.Utils.bifunction{t,u,r} :: (t -> u -> r) -> BiFunction t u r

The code to make a BiFunction from a Frege function is the following (and is provided in frege.run8.Utils):

public static<T,U,R> BiFunction<T, U, R> bifunction(Func.U<T, Func.U<U, R>> f) {
    return (T a, U b) -> f
        .apply(Thunk.lazy(a))
        .call()
        .apply(Thunk.lazy(b))
        .call();    
}

Now suppose we want to enrich the Java ecosystem with a bi-functor that runs the const function:

public static<A,B> BiFunction<A, B, A> biconst() {
    return bifunction(
            (Func.U<A, Func.U<B,A>>)
            ((Lazy<A> a) -> (Func.U<B, A>)((Lazy<B> b) -> Thunk.lazy(
                    PreludeBase.<A, B>$const(a.call(), b)))));
}

Too easy, yet in Frege this is, as always, a tiny bit more concise given the definitions from above:

biconst = BiFunction.from const

Dealing with Monads

Here is the ultimate guide how to handle monadic values.

It is important to follow the rules laid down in the following paragraphs. Please consider: even if your IDE discloses the actual representation of IO actions, it is unwise to employ this knowledge. The representation can change with the next release, and your code will be worthless. However, the recipes given below shall work regardless of the actual representation.

IO and ST

When dealing with IO actions, it is important to keep the types in mind. Here is an easy example:

putStrLn :: String -> IO ()    -- not an IO action, but a function that constructs one
putStrLn "foo" :: IO ()        -- this is an IO action, actually

Construction of an IO action does not execute it yet. To actually run the action, we need to use IO.performUnsafe thus:

PreludeBase.TST.<>performUnsafe( Prelude.putStrLn("foo") ).call();

The trailing call is needed only when the action gives back a useful result.

Note how the construction of the action in the argument of the performUnsafe method avoids writing down the Java type associated with IO (). All we need to know is that performUnsafe will take any IO or ST action. Also, the "diamond operator" should infer the result type of the IO action.

From the point of view of Java code calling into Frege, ST actions are just IO actions, just like in Frege where you can use ST actions freely inside IO actions.

Maybe, Either and lists

These are easy monads whose representation is the same as the corresponding data types. See the section about data types below to learn how to work with algebraic data types.

State and StateT

Consider the following function constructing a State action that increments its Int state by the amount given and returns a Bool (this return value is just there to make the difference between the state and the result of running a state action better visible):

increment ∷ Int → State Int Bool
increment n = State.modify (n+) >> pure true

To run a State action, we have three convenience functions in frege.control.monad.State

  • runState takes a State action and an initial state, and returns the result and the new state in a tuple
  • execState takes a State action and an initial state but returns only the new state
  • evalState takes a State action and an initial state but returns only the result

As before, we need to construct the action first and pass it to one of the functions mentioned, along with a state. If this is done in one go, we again are independent of the actual Java representation of a State action.

import frege.prelude.PreludeBase;
import frege.prelude.PreludeBase.TTuple2;
import frege.control.monad.State;

TTuple2<Boolean, Integer> result = State.TState.<Boolean, Integer>run(
                               increment(2),            // construct action
                               Thunk.<Integer>lazy(40)  // initial state
                           ).call();
System.out.println("new state is " + PreludeBase.<Boolean, Integer>snd(result));

The only difficulty here is that runState is aliased to State.run and this appears then in Java as State.TState.run. Its cousins evalState and execState are not aliased and translate to State.evalState and State.execState.

StateT is very similar to State, the difference is just that the convenience functions runStateT, evalSTateT and execStateT return another monadic value. Suppose we have

incr ∷ Int → StateT Int IO ()
incr n = do
    st ← StateT.get
    StateT.put (st+n)
    liftIO . putStrLn $ (if odd (st+n) then "new state is odd" else "new state is even")

then execStateT will return a IO action that, when executed, returns the new state. Unfortunately, we need to learn two more things before we can do this in Java.

Clone this wiki locally