Why is there a difference between Java8 and Scala2.12 lambda cache?
Asked Answered
P

1

7

Java code

package lambda_cache_example_java;

interface Semigroup1<A> {
  public A append(A a1, A a2);
}

interface Semigroup2<A> {
  public A append(A a1, A a2);

  public interface Foo{}
  public class Bar{}
}

class Main {
  static Semigroup1<Integer> intSemigroup1() {
    return (a1, a2) -> a1 + a2;
  }

  static Semigroup2<Integer> intSemigroup2() {
    return (a1, a2) -> a1 + a2;
  }

  public static void main(String[] args) {
    Semigroup1<Integer> x1 = intSemigroup1();
    Semigroup1<Integer> x2 = intSemigroup1();
    System.out.println(x1);
    System.out.println(x2);
    System.out.println(x1 == x2); // same instance

    Semigroup2<Integer> y1 = intSemigroup2();
    Semigroup2<Integer> y2 = intSemigroup2();
    System.out.println(y1);
    System.out.println(y2);
    System.out.println(y1 == y2); // same instance as well
  }
}

Scala code (version 2.12.0)

package lambda_cache_example_scala

trait Semigroup1[A] {
  def append(a1: A, a2: A): A
}

trait Semigroup2[A] {
  def append(a1: A, a2: A): A

  trait Foo
}

object Main {
  def intSemigroup1(): Semigroup1[Int] =
    (a1, a2) => a1 + a2

  def intSemigroup2(): Semigroup2[Int] =
    (a1, a2) => a1 + a2

  def main(args: Array[String]): Unit = {
    val x1 = intSemigroup1()
    val x2 = intSemigroup1()
    println(x1)
    println(x2)
    println(x1 eq x2) // same instance

    val y1 = intSemigroup2()
    val y2 = intSemigroup2()
    println(y1)
    println(y2)
    println(y1 eq y2) // not same
  }
}

result

$ sbt "runMain lambda_cache_example_java.Main" "runMain lambda_cache_example_scala.Main"
[info] Running lambda_cache_example_java.Main 
lambda_cache_example_java.Main$$Lambda$9/1908283686@44939bb7
lambda_cache_example_java.Main$$Lambda$9/1908283686@44939bb7
true
lambda_cache_example_java.Main$$Lambda$10/2119574930@7f206457
lambda_cache_example_java.Main$$Lambda$10/2119574930@7f206457
true
[success] Total time: 0 s, completed 2016/11/24 15:09:56
[info] Running lambda_cache_example_scala.Main 
lambda_cache_example_scala.Main$$$Lambda$11/2085010450@7b408c6e
lambda_cache_example_scala.Main$$$Lambda$11/2085010450@7b408c6e
true
lambda_cache_example_scala.Main$$anonfun$intSemigroup2$2@c5329e5
lambda_cache_example_scala.Main$$anonfun$intSemigroup2$2@752d3cd9
false
[success] Total time: 0 s, completed 2016/11/24 15:09:57
Pietrek answered 24/11, 2016 at 6:36 Comment(0)
M
3

Scala has path-dependent types. Although not obvious from your example, one could construct nested traits where the trait Foo inside of one for instance of Semigroup2 is not at all compatible with the the Foo from another instance of Semigroup2. This post and this answer seem like good explanations of path dependent types.

This means that an instance of Semigroup2 is defined also by its inner trait, so a closure must be made when referencing one of its methods. Since that closure is re-made on the fly every time we try to reference that method, it isn't surprising the anonymous functions are different.

In Java, this is not the case. Semigroup2<A>.Foo is a type (unlike in Scala where you need an instance of Semigroup[A] to identify a type Foo).

Mannino answered 24/11, 2016 at 7:57 Comment(4)
The closest Java equivalent for Scala's behaviour would be if Main::intSemigroup2's lambda capture a Semigroup2.Foo instance, like (a1 + a2) -> { foo.hashCode(); return a1 + a2; }). This would force Main::intSemigroup2 to always return a new lambda, since Java has no guarantees that the captured Foo value is inline-able.Stockpile
@Stockpile I suppose so - I thought of the question more as "why isn't Scala returning the same function" as opposed to "why isn't Java returning different functions. Good point.Mannino
The last paragraph needs a correction. In Java, nested interfaces are always implicitly static, so Semigroup2<A>.Foo is not a type, there is only Semigroup2.Foo, a single interface. In contrast, the type Semigroup2<A>.Bar is generic, so conceptually, Semigroup2<String>.Bar and Semigroup2<Integer>.Bar are distinct types, but due to type erasure, there will be only one Class representing Semigroup2.Bar.Hellbent
@srborlongan: That’s an implementation detail, not a concept. In principle, the JVM is allowed to fold these instances into one, if it can prove that they will exhibit the same behavior. It must be emphasized that in Java, the identity of instances created for lambda expressions is intentionally unspecified and must not be confused with the formal logic. E.g. Function.<String>identity()==Function.<Integer>identity() will be rejected by the compiler due to incompatible types, despite the objects are the same, as provable via Function.<String>identity()==(Object)Function.<Integer>identity().Hellbent

© 2022 - 2024 — McMap. All rights reserved.