Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specialize on a type projection in Scala?

Tags:

types

scala

Statement of the question

Consider a type T that contains an abstract type member A:

trait T {
  type A
}

I'd like to create a class that takes a T0 <: T as a type parameter, but specializes on the type projection T0#A. For example, in the following, can the method foo be specialized?

class Foo[T0 <: T] {
  def foo(a: T0#A, f: T0#A => T0#A) = f(a)
}

Note that annotating T0 with @specialized will not achieve the desired result. Is there is a good way to specialize foo on the type projection T#A?

A limited solution: inherit from specialized parent class with extra parameter

In this particular case, here's a way to specialize on T0#A:

trait SpecializedFoo[@specialized A0, T0 <: T] {
  def foo(a: A0, f: A0 => A0) = f(a)
}
class Foo2[T0 <: T] extends SpecializedFoo[T0#A, T0]

By inheriting from the specialized parent class SpecializedFoo, we ensure that Foo2.foo is specialized.

Verification of specialization

To verify that Foo2.foo, but not Foo.foo, is specialized, we can call them with an explicit T where T#A is a primitive Double,

trait ExplicitT extends T {
  type A = Double
}

object Test {
  def test1 = (new Foo[ExplicitT]).foo(1.0, _ + 1.0)
  def test2 = (new Foo2[ExplicitT]).foo(1.0, _ + 1.0)
}

The bytecode can be examined from the REPL with the command ":javap -v Test",

public double test1();
  Code:
   Stack=4, Locals=1, Args_size=1
   0:   new #16; //class Foo
   3:   dup
   4:   invokespecial   #18; //Method Foo."<init>":()V
   7:   dconst_1
   8:   invokestatic    #24; //Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
   11:  new #26; //class Test$$anonfun$test1$1
   14:  dup
   15:  invokespecial   #27; //Method Test$$anonfun$test1$1."<init>":()V
   18:  invokevirtual   #31; //Method Foo.foo:(Ljava/lang/Object;Lscala/Function1;)Ljava/lang/Object;
   21:  invokestatic    #35; //Method scala/runtime/BoxesRunTime.unboxToDouble:(Ljava/lang/Object;)D
   24:  dreturn
  LineNumberTable: 
   line 13: 0


public double test2();
  Code:
   Stack=5, Locals=1, Args_size=1
   0:   new #38; //class Foo2
   3:   dup
   4:   invokespecial   #39; //Method Foo2."<init>":()V
   7:   dconst_1
   8:   new #41; //class Test$$anonfun$test2$1
   11:  dup
   12:  invokespecial   #42; //Method Test$$anonfun$test2$1."<init>":()V
   15:  invokeinterface #48,  4; //InterfaceMethod SpecializedFoo.foo$mcD$sp:(DLscala/Function1;)D
   20:  dreturn
  LineNumberTable: 
   line 14: 0

Note that boxing appears in test1 but not test2.

Limitations

Edit 7/9 The trick above is more limited than I realized at first. It won't work at all for specializing this case:

trait T {
  type A
  def x: A
  def f: A => Double
}

class Foo[T0 <: T] {
  def foo(t: T0) = t.f(t.x)
}

I see no reason why a (hypothetical) compiler couldn't specialize on A in principle; a usual, the specialized versions would only be usable when a specific T#A is known at compile time. The natural practical solution is to lift A into a type parameter of T, but I was wondering if I could avoid that.

like image 893
Kipton Barros Avatar asked Jul 08 '11 22:07

Kipton Barros


2 Answers

I can't see how that could possibly work. Specialization is done when compiling the class, and, at that time, A isn't known.

like image 52
Daniel C. Sobral Avatar answered Oct 12 '22 23:10

Daniel C. Sobral


This is a compiler limitation; one cannot generally specialize on elements of a type parameter. However, the proposed trick is good enough for my purposes:

trait Types {
  type A
  type B
}

trait GenOps[@specialized A, @specialized B] {
  ...
}

trait Ops[T <: Types] extends GenOps[T#A, T#B]

This way the trait Ops gets specialized because it inherits the specialized implementations in trait GenOps. My motivation is that I want trait Ops to take a single type parameter T, rather than both T#A and T#B (this becomes necessary when Ops also takes a higher kinded type that expects T as a parameter).

like image 21
Kipton Barros Avatar answered Oct 13 '22 00:10

Kipton Barros