Parameterized enums in scala 3 accessed from Java

Hi all,

I am writing a Scala 3 library, with the requirement that has to be used within Java projects as well.

I was liberally using Enum in my code… until I discovered that Scala 3 enumerations, if parameterized, seem hard or impossible to use from Java code (right now I am on the impossible side, since I have not found a solution, but maybe it exists).

I have two different general cases:

  enum Foo:

    case FooAdd(number: Int)
    case Undo

and

  enum Bar(val x: List[Int], val y: String):

    case BarOne(z: List[Int]) extends Bar(z, "one")
    case BarTwo(first: Int)   extends Bar(List(first), "two")

Which is the recommended way to design such code if has to be accessed from Java?
Hopefully someone has already been there and can point me in the right direction.

Thank you in advance,

Mario

You don’t state your use case (what you tried that doesn’t work).

enum Foo:
  case Add(n: Int)
  case Undo

enum Bar(val xs: List[Int], s: String):
  case One(z: List[Int]) extends Bar(z, "one")
  case Two(n: Int) extends Bar(List(n), "two")

and

public class FooBar {
        public String f(int n) { return Foo$Add.apply(n).toString(); }
}

and

@main def test() = println:
  FooBar().f(42)

just works, except for the part where I left out the parens on toString, of course.

The docs just say don’t do that, and link to a boring test for java.lang.Enum.

You can’t even say Enum without specifying which one (in ordinary communication).

Thank you @som-snytt

Let me start saying that I am well versed in Scala but I don’t know anything about Java, so most likely I am doing every kind of wrong things here…

My ultimate goal would be to call from Java a method currently defined in the Scala library as

def create(bar: Bar, foos: List[Foo]): Double = ???

But my actual use case sits a little before.
I succeeded in creating an Undo with

Foo foo = Foo.fromOrdinal(1);

but I don’t succeed in creating an Add().

Your suggestion of Foo$Add.apply(n) doesn’t seem to work for me, I get

error: cannot find symbol
        public String f(int n) { return Foo$Add.apply(n).toString(); }
                                        ^
  symbol:   variable Foo$Add
  location: class Main.FooBar

and I am importing:

import [...].Foo;
import [...].Foo.*; 
import [...].Foo$.*; // just trying if could help
// import [...].Foo$Add; // gives error

I am editing and running in the latest IntelliJ using JDK 21.

According to the docs you should extend Enum like so:

enum Color extends Enum[Color] { case Red, Green, Blue }

in order to be able to get a “normal” Java-enum from a JDK point of view and then it should be usable from Java I guess.
The doc page that @som-snytt linked to is here:

https://dotty.epfl.ch/docs/reference/enums/enums.html#compatibility-with-java-enums-1

Hello @bjornregnell, from what I understand this is ok for non-parameterized enums.

In fact I have those and they just work as intended.
In the Scala lib:

  enum Whatever extends java.lang.Enum[Whatever]:

    case Sun, Moon

and from Java

Whatever whatever = Whatever.valueOf("Sun");

In the java-test from the doc that @som-snytt linked to:

there are indeed only simple cases in the example… But perhaps there is a way, I don’t know exactly how.

I think I would just create the enum-part in java and just put the java file alongside your scala-code in the build. It seems like many scala-projects that want to offer also a java-api offer that from Java as a facade calling into Scala to make the Java experience nice.

@bjornregnell thank you for your interesting suggestions. In my case I could also add less parameterized methods to my scala-code and make the Java experience nice.

But still I would like to understand if there is a way. I bet there is. If there is not, I would say that some words of caution could be added somewhere into the documentation. After all in “Tour of Scala” it’s stated:

In particular, the interaction with the mainstream object-oriented Java programming language is as seamless as possible.

1 Like

Yes the interaction with Java from Scala. But the other way around there are many Scala features which are at least very impractical to use from Java, or probably even impossible in the case of metaprogramming features.

Fair point. As I wrote this is the first time I have to plan and worry about how a library of mine will be used from Java, and probably I was under the naive impression (or hope) that whatever I wrote could be somehow reached.

The original questions remain: is there an alternative recommended way to design something similar to a parameterized Scala 3 Enum if it has to be accessed from Java?

For my example, I just used javac and scalac. (JDK 22 and Scala 3.4.2.)

Getting a “mixed” project to compile can be tricky. I don’t know what “knobs” are available in the IDE.

The other choice is to have separate projects for Java and Scala code, such that the dependencies are “one-way”.

If you don’t need to “create” values in Java code but merely access them, then it is natural for the enum class to implement a useful interface. Then you’d use only that superinterface from Java.

If you need to create values, maybe have a “facade” in Scala to forward the calls:

object JavaAPI:
  def createAdd(): Foo = Add(42)

used statically JavaAPI.createAdd(); from Java, hiding the nested class type.

A common recommendation is to write java api in java. (I don’t have the direct experience you asked for, aside from toy examples.)

I’m away from a big computer, but did you try:

Foo.Add.apply(n)?

Something similar happens here where a val in a companion Scala object is accessed by Java code.

Granted, the Java code is compiled in a mixed Java / Scala project, so I think it uses scalac to compile Java, but it’s worth a try…

EDIT: I just read the previous post from @som-snytt. That already covers this, only I’ve glossed over the nested name aspect.

1 Like

Thank you all for your inputs.

@som-snytt @sageserpent-open Doesn’t work for me yet, I tried with JDK 22 and Scala 3.4.2, also putting the Java code within the library itself, but nothing. At the end of the day I think the most relevant test is using the packaged JAR in a separate Java project, and there as well I cannot find a way to create Foo.Add(1).

So, yes I am going for the Scala facade. It is not ideal because I have some enum Bar with potentially a dozen or more of parameterized cases, but I can cover all of them with one dedicated simplified method each.

In terms of writing the java api in java, it seems to me that I am stuck in the loop: if I cannot access Foo and Bar from java, I cannot write in java simplified methods.

I think the proposal by @som-snytt to make a facade in Scala that is nice from Java seems to be the best solution given what I have understood from your use case. The challenge will be to restrict yourself in the api of the facade to a subset of Scala that is nice from Java. You have to do away with stuff like defult args or what not in the methods you expose. Make it as simple as possible in a Java fashion and keep it as high-level as possible where all the interesting stuff happens under the hood in Scala hidden from the Java-side. I would be interesetd in reading about how it goes for you in this thread, if you are happy to share it, and I suspect you are not alone in your use case :slight_smile: Good luck!

2 Likes

Perhaps also use make heavy use of JavaConverters to connect better with the JDK at the Java user side.

1 Like

did you try Foo.Add.apply(n)?
Edit: ok seems someone else did suggest that - but this is the correct representation if you look at the bytecode