oracle / graal

GraalVM compiles Java applications into native executables that start instantly, scale fast, and use fewer compute resources 🚀
https://www.graalvm.org
Other
20.15k stars 1.61k forks source link

[polyglot] scala sdk support for data types conversions #2314

Open aitorhh opened 4 years ago

aitorhh commented 4 years ago

Feature request

Is your feature request related to a problem? Please describe. The problem is when trying to use graal sdk library withing Scala code. The data conversions between Graal <-> Java <-> Scala are not trivial. Or I am missing something?

Describe the solution you'd like.

I would like to have specific functions to convert org.graalvm.polyglot.Value to the Scala primitives and/or collections. At the moment, we can only see "asHostInstance" (https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html), which do not apply properly to Scala.

Describe who do you think will benefit the most. GraalVM Polyglot developers and developers of libraries and frameworks depending on Graal.

Describe alternatives you've considered. Looking into possibilities around

import scala.collection.JavaConversions._
import scala.collection.JavaConverters._

And if reflect could help:

import scala.reflect.runtime.universe._
boris-spas commented 4 years ago

Tracking internally as Issue GR-23079.

chumer commented 4 years ago

Hey @aitorhh . Thank you very much for the report!

We want to to improve Scala support. Would you mind describing what you mean by " The data conversions between Graal <-> Java <-> Scala are not trivial. " by providing a few examples?

In the meantime I suggest you implement converters to ProxyObject / ProxyArray for Scala objects that you pass in with the behavior you desire.

aitorhh commented 4 years ago

Hi @chumer, sorry for my late reply. I am taking the issue again.

To begin with, for the "graal" -> "java" I use the following example (in scala):

object RuntimeGraalTestAsMain{

    def main(args: Array[String]): Unit = {
        import org.scalatest.Assertions._

        val context = Context.newBuilder()
                             .allowAllAccess(true)
                             .build()
        assert(context.eval("js", "undefined").as(classOf[Object]) == null)
        assert(context.eval("js", "'foobar'").as(classOf[String]) == "foobar")
        assert(context.eval("js", "42").as(classOf[Integer]) == 42)
        assert(context.eval("js", "{foo:'bar'}").as(classOf[java.util.Map[String, String]]).get("foo") ==     "bar")
        assert(context.eval("js", "[42]").as(classOf[java.util.List[Integer]]).get(0) == 42)
    }
}

Without going into more complicated examples the above, is already failing:

Exception in thread "main" java.lang.ClassCastException: Cannot convert 'bar'(language: JavaScript, type: string) to Java type 'java.util.Map': Unsupported target type.                                             
        at org.graalvm.truffle/com.oracle.truffle.polyglot.HostInteropErrors.newClassCastException(HostInteropErrors.java:195)                                                                                       
        at org.graalvm.truffle/com.oracle.truffle.polyglot.HostInteropErrors.cannotConvertPrimitive(HostInteropErrors.java:66)                                                                                       
        at org.graalvm.truffle/com.oracle.truffle.polyglot.ToHostNode.convertImpl(ToHostNode.java:215)                                                                                                               
        at org.graalvm.truffle/com.oracle.truffle.polyglot.ToHostNode.doGeneric(ToHostNode.java:128)                                                                                                                 
        at org.graalvm.truffle/com.oracle.truffle.polyglot.ToHostNodeGen$Uncached.execute(ToHostNodeGen.java:250)                                                                                                    
        at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotValue$PrimitiveValue.as(PolyglotValue.java:2134)                                                                                                  
        at org.graalvm.sdk/org.graalvm.polyglot.Value.as(Value.java:974)                                                                                                                                             
        at iot.cluster.RuntimeGraalTest$.main(RuntimeGraalTest.scala:188)                                                                                                                                            
        at iot.cluster.RuntimeGraalTest.main(RuntimeGraalTest.scala)     

For the "scala" -> "graal", here is an example (of course, this does not compile as is):

object RuntimeGraalTestAsMain{

    def main(args: Array[String]): Unit = {
        import org.scalatest.Assertions._

        val context = Context.newBuilder()
                             .allowAllAccess(true)
                             .build()
    val executeFunction = "x => {return x;}"

    val source = Source
          .newBuilder("js", executeFunction.toCharArray, "inline.js")
          .build()
    val function = context.eval(source)
    assert(function.execute(null) == "undefined")
    assert(function.execute("foobar") == "foobar")
    assert(function.execute(42) == 42)
    assert(function.execute(Map[String, String]("foo" -> "bar")) == Map[String, String]("foo" -> "bar"))
    assert(function.execute(List(Map[String, String]("foo" -> "bar"))) == List(Map[String, String]("foo" -> "bar")))
    assert(function.execute(List(42)) == List(42))

    }
}

I will look into the ProxyObject and ProxyArray. I complete missed that interface before :)