Calling Scala Monads from Java #map

我怕爱的太早我们不能终老 提交于 2019-12-23 08:33:11

问题


I have an instance of a scala.collection.immutable.List and I want to call the map method on it, BUT from Java.

I need to supply a CanBuildFrom.

I noticed that a lot of the scala collections companion objects contain implicit CanBuildFrom instances, but I cannot work out which one I need to use.

Here is my Java code:

    Function1<WeatherData, BigDecimal> mapper = new AbstractFunction1<WeatherData, BigDecimal>(){
        @Override
        public BigDecimal apply(WeatherData data) {
            return data.getTemps().reduce(adder).divide(new BigDecimal(data.getTemps().size()));
        }
    };

    scala.collection.immutable.List<WeatherData> data = ...

    data.map(mapper, ???);

What should I pass as a CanBuildFrom (the second parameter?)

PS Using Scala 2.10-M5


回答1:


You can actually get the types right in Java without too much fuss:

import scala.collection.Traversable;
import scala.collection.generic.CanBuildFrom;
import scala.collection.immutable.List;
import scala.collection.mutable.Builder;
import scala.runtime.AbstractFunction1;

public class ScalaMapTest {
  public static List<Integer> parseInts(List<String> xs) {
    final CanBuildFrom<List<?>, Integer, List<Integer>> builder =
      List.<Integer>canBuildFrom();

    return xs.map(
      new AbstractFunction1<String, Integer>() {
        public Integer apply(String s) {
          return Integer.parseInt(s);
        }
      },
      new CanBuildFrom<Traversable<String>, Integer, List<Integer>>() {
        public Builder<Integer, List<Integer>> apply() {
          return builder.apply();
        }

        public Builder<Integer, List<Integer>> apply(Traversable<String> from) {
          return builder.apply(from.toList());
        }
      }
    );
  }
}

It's still ugly as sin, but it works. The problem is that wildcard on the CanBuildFrom you get from the canBuildFrom method on the List object, but fortunately you can create your own CanBuildFrom wrapper with the right type.




回答2:


If you want to know what scalac does with the code then ask it. ;)

This is possible with either scalac -Xprint:typer <file> or the new Reflection API in 2.10:

scala> import reflect.runtime.universe._
import reflect.runtime.universe._

scala> reify{List(1,2,3).map(_+1)}
res0: reflect.runtime.universe.Expr[List[Int]] = Expr[List[Int]](immutable.this.List.apply(1, 2, 3).map(((x$1) => x$1.$plus(1)))(immutable.this.List.canBuildFrom))

Thus, call map with this CanBuildFrom and all works fine. Does it really? No, it doesn't! The problem is that the Java compiler is to silly to infer the arguments expected by map. So what to do? I believe the only way is to create the required values and them cast them to death. Finally mix some SuppressWarnings-Annotations in and the code should work fine. ;)

This is what I came up with:

import scala.Function1;
import scala.collection.generic.CanBuildFrom;
import scala.collection.immutable.List;
import scala.collection.immutable.List$;
import scala.runtime.AbstractFunction1;

public class JTest {
  @SuppressWarnings({"unchecked", "rawtypes"})
  public static void main(final String... args) {
    final List<Integer> xxx = (List) List$.MODULE$.apply(Predef.wrapIntArray(new int[] {1,2,3}));
    System.out.println(xxx);

    System.out.println(Test.sum(1, 2));
    final Abc abc = new Abc();
    System.out.println(abc.hello("simon"));

    final List<Integer> xs = (List) Test.xs();
    final Function1<Integer, String> mapper = new AbstractFunction1<Integer, String>() {
      @Override
      public String apply(final Integer i) {
        return String.valueOf(i);
      }
    };
    final CanBuildFrom<List<Integer>, String, List<String>> cbf =
        (CanBuildFrom) List.<Integer>canBuildFrom();
    final List<String> ys = xs.<String, List<String>>map(mapper, cbf);
    System.out.println(ys);
  }
}

The list:

object Test {
  def xs = List(1,2,3)
}

I believe, the best thing is not to use Scala code from Java. It looks ugly. At least, wrap it in some helper classes so that someone who sees this code does not think about an acid attack on his retina.

By the way, sometimes you have to look at the Bytecode to understand how scalac creates values. If you want to create a Scala List is Java you have to decompile the code with javap -c -s -l -verbose -private <classfile>:

   0:   aload_0
   1:   invokespecial   #20; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   putstatic   #22; //Field MODULE$:LX$;
   8:   aload_0
   9:   getstatic   #27; //Field scala/collection/immutable/List$.MODULE$:Lscala/collection/immutable/List$;
   12:  getstatic   #32; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   15:  iconst_3
   16:  newarray int
   18:  dup
   19:  iconst_0
   20:  iconst_1
   21:  iastore
   22:  dup
   23:  iconst_1
   24:  iconst_2
   25:  iastore
   26:  dup
   27:  iconst_2
   28:  iconst_3
   29:  iastore
   30:  invokevirtual   #38; //Method scala/LowPriorityImplicits.wrapIntArray:([I)Lscala/collection/mutable/WrappedArray;
   33:  invokevirtual   #42; //Method scala/collection/immutable/List$.apply:(Lscala/collection/Seq;)Lscala/collection/immutable/List;
   36:  new #44; //class X$$anonfun$1

Or in more readable Java code:

@SuppressWarnings({"unchecked", "rawtypes"})
final List<Integer> xs = (List) List$.MODULE$.apply(Predef.wrapIntArray(new int[] {1,2,3}));


来源:https://stackoverflow.com/questions/11678756/calling-scala-monads-from-java-map

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!