Постановка вопроса
Рассмотрим тип T
, который содержит член абстрактного типа A
:
trait T {
type A
}
Я хотел бы создать класс, который принимает T0 <: T
в качестве параметра типа, но специализируется на проекции типа T0 # A
. Например, может ли метод foo
быть специализированным ниже?
class Foo[T0 <: T] {
def foo(a: T0#A, f: T0#A => T0#A) = f(a)
}
Обратите внимание, что аннотирование T0
с помощью @specialized
не приведет к желаемому результату. Есть ли хороший способ специализации foo
на проекции типа T # A
?
Ограниченное решение: наследование от специализированного родительского класса с дополнительным параметром
В данном конкретном случае случае, вот способ специализации на 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]
Наследуя специализированный родительский класс SpecializedFoo
, мы гарантируем, что Foo2.foo
специализирован .
Проверка специализации
Чтобы проверить, что Foo2.foo
, но не Foo.foo
, является специализированным, мы можем вызвать их с явным T
, где T # A
- примитивный тип 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)
}
Байт-код можно проверить из REPL с помощью команды «: 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
Обратите внимание, что бокс появляется в test1
, но не test2
.
Ограничения
Изменить 7/9 Приведенный выше трюк более ограничен, чем я думал вначале. Это вообще не сработает для специализации этого случая:
trait T {
type A
def x: A
def f: A => Double
}
class Foo[T0 <: T] {
def foo(t: T0) = t.f(t.x)
}
Я не вижу причин, по которым (гипотетический) компилятор не мог бы специализироваться на A
в принципе ; Как правило, специализированные версии можно будет использовать только тогда, когда во время компиляции известен конкретный T # A
. Естественным практическим решением является преобразование A
в параметр типа T
, но мне было интересно, могу ли я этого избежать.