35

Suppose I have the following class:

public class FixExpr {
  Expr<FixExpr> in;
}

Now I want to introduce a generic argument, abstracting over the use of Expr:

public class Fix<F> {
  F<Fix<F>> in;
}

But Eclipse doesn't like this:

The type F is not generic; it cannot be parametrized with arguments <Fix<F>>

Is this possible at all or have I overlooked something that causes this specific instance to break?

Some background information: in Haskell this is a common way to write generic functions; I'm trying to port this to Java. The type argument F in the example above has kind * -> * instead of the usual kind *. In Haskell it looks like this:

newtype Fix f = In { out :: f (Fix f) }
Ben Lings
  • 28,823
  • 13
  • 72
  • 81
Martijn
  • 6,713
  • 3
  • 31
  • 38
  • 1
    What is the actual problem you are trying to solve with this? Could it not be more easily solved using a template? – KitsuneYMG May 18 '09 at 12:48

6 Answers6

31

I think what you're trying to do is simply not supported by Java generics. The simpler case of

public class Foo<T> {
    public T<String> bar() { return null; }
}

also does not compile using javac.

Since Java does not know at compile-time what T is, it can't guarantee that T<String> is at all meaningful. For example if you created a Foo<BufferedImage>, bar would have the signature

public BufferedImage<String> bar()

which is nonsensical. Since there is no mechanism to force you to only instantiate Foos with generic Ts, it refuses to compile.

Zarkonnen
  • 22,200
  • 14
  • 65
  • 81
  • It is not directly supported by Java but you can still encode that concept in Java's type system. See my answer below. – michid Jan 02 '21 at 22:03
27

Maybe you can try Scala, which is a functional language running on JVM, that supports higher-kinded generics.


[ EDIT by Rahul G ]

Here's how your particular example roughly translates to Scala:

trait Expr[+A]

trait FixExpr {
  val in: Expr[FixExpr]
}

trait Fix[F[_]] {
  val in: F[Fix[F]]
}
Community
  • 1
  • 1
ZelluX
  • 69,107
  • 19
  • 71
  • 104
4

In order to pass a type parameter, the type definition has to declare that it accepts one (it has to be generic). Apparently, your F is not a generic type.

UPDATE: The line

F<Fix<F>> in;

declares a variable of type F which accepts a type parameter, the value of which is Fix, which itself accepts a type parameter, the value of which is F. F isn't even defined in your example. I think you may want

Fix<F> in;

That will give you a variable of type Fix (the type you did define in your example) to which you are passing a type parameter with value F. Since Fix is defined to accept a type parameter, this works.

UPDATE 2: Reread your title, and now I think you might be trying to do something similar to the approach presented in "Towards Equal Rights for Higher-Kinded Types" (PDF alert). If so, Java doesn't support that, but you might try Scala.

Jacob van Lingen
  • 8,989
  • 7
  • 48
  • 78
Hank Gay
  • 70,339
  • 36
  • 160
  • 222
  • Can you please elaborate a bit? I have declared the type to accept a type parameter -- Fix. Or is this not what you mean? – Martijn May 18 '09 at 10:00
  • But the type you have picked - > - is not generic; it only works for Fix - you should declare it as type , surely? – sanbikinoraion May 18 '09 at 10:08
  • You're welcome. I'm not well-versed in type theory, so it's been heavy going so far, but it is quite interesting. I'm glad I found your question this morning or I might never have read the paper. – Hank Gay May 18 '09 at 11:34
  • 1
    Seconding the suggestion you look at Scala. You can intermix Scala and Java fairly freely, so it's easy enough to dip in your toes. – Zarkonnen May 18 '09 at 12:04
  • re "intermix freely": probably won't be able to use a Scala higher kinded type from Java though. – Erik Kaplun Feb 20 '14 at 16:18
2

Still, there are ways to encode higer-kinded generics in Java. Please, have a look at higher-kinded-java project.

Using this as a library, you can modify your code like this:

public class Fix<F extends Type.Constructor> {
    Type.App<F, Fix<F>> in;
}

You should probably add an @GenerateTypeConstructor annotation to your Expr class

@GenerateTypeConstructor
public class Expr<S> {
    // ...
}

This annotation generates ExprTypeConstructor class. Now you can process your Fix of Expr like this:

class Main {
    void run() {
        runWithTyConstr(ExprTypeConstructor.get);
    }

    <E extends Type.Constructor> void runWithTyConstr(ExprTypeConstructor.Is<E> tyConstrKnowledge) {
        Expr<Fix<E>> one = Expr.lit(1);
        Expr<Fix<E>> two = Expr.lit(2);

        // convertToTypeApp method is generated by annotation processor
        Type.App<E, Fix<E>> oneAsTyApp = tyConstrKnowledge.convertToTypeApp(one);
        Type.App<E, Fix<E>> twoAsTyApp = tyConstrKnowledge.convertToTypeApp(two);

        Fix<E> oneFix = new Fix<>(oneAsTyApp);
        Fix<E> twoFix = new Fix<>(twoAsTyApp);

        Expr<Fix<E>> addition = Expr.add(oneFix, twoFix);
        process(addition, tyConstrKnowledge);
    }

    <E extends Type.Constructor> void process(
            Fix<E> fixedPoint,
            ExprTypeConstructor.Is<E> tyConstrKnowledge) {

        Type.App<E, Fix<E>> inTyApp = fixedPoint.getIn();

        // convertToExpr method is generated by annotation processor
        Expr<Fix<E>> in = tyConstrKnowledge.convertToExpr(inTyApp);

        for (Fix<E> subExpr: in.getSubExpressions()) {
            process(subExpr, tyConstrKnowledge);
        }
    }

}
Victor Nazarov
  • 825
  • 8
  • 11
1

There is a roundabout way to encode higher kinded types in Java as pointed out by Victor. The gist of it is to introduce a type H<F, T> to encode F<T>. This can then be used to encode fixed point of functors (i.e. Haskell's Fix type):

public interface Functor<F, T> {
    <R> H<F, R> map(Function<T, R> f);
}

public static record Fix<F extends H<F, T> & Functor<F, T>, T>(F f) {
    public Functor<F, Fix<F, T>> unfix() {
        return (Functor<F, Fix<F, T>>) f;
    }
}

From here you can go on and implement catamorphisms over initial algebras:

public interface Algebra<F, T> extends Function<H<F, T>, T> {}

public static <F extends H<F, T> & Functor<F, T>, T> Function<Fix<F, T>, T> cata(Algebra<F, T> alg) {
    return fix -> alg.apply(fix.unfix().map(cata(alg)));
}

See my GitHub repo for working code including some example algebras. (Note, IDE's like IntelliJ struggle with the code although it compiles and runs just fine with Java 15).

michid
  • 10,536
  • 3
  • 32
  • 59
1

It looks as if you may want something like:

public class Fix<F extends Fix<F>> {
    private F in;
}

(See the Enum class, and questions about its generics.)

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • Hi Tom, that solution looks really interesting but I don't really get yet what it does. Doesn't that impose some hierarchy on F? I don't want to do that -- all I ask of F is that it still receives a type argument to be complete. – Martijn May 18 '09 at 11:30
  • if thats the case, then it is not possible in java to do what you want - the language is not expressive enough. – Chii May 18 '09 at 11:58
  • It says that F is a type of Fix. Because Fix is generic it needs to be given the correct generic argument. Your question is extremely abstract. I understand Haskell has a complicated type system that is different to Java's. It's not surprising that there is not a 1:1 map for every feature. – Tom Hawtin - tackline May 18 '09 at 12:00