Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Scala Monads from Java #map

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

like image 487
John Smith Avatar asked Jul 26 '12 22:07

John Smith


2 Answers

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.

like image 111
Travis Brown Avatar answered Nov 15 '22 07:11

Travis Brown


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}));
like image 25
kiritsuku Avatar answered Nov 15 '22 06:11

kiritsuku