Верхняя и нижняя границы типа scala

Рассмотрим следующую иерархию:

class C1
class C2 extends C1
class C3 extends C2
class C4 extends C3

Я хочу написать функцию, которая просто принимает типы C2 и C3. Для этого я подумал о следующем:

 def f [C >: C3 <: C2](c :C) = 0

Я ожидаю следующего поведения

f(new C1)  //doesn't compile, ok
f(new C2)  //compiles, ok
f(new C3)  //compiles, ok
f(new C4)  // !!! Compiles, and it shouldn't 

Проблема заключается в вызове с C4, что я не хочу разрешать, но компилятор принимает. Я понимаю, что C4 <: C2 правильно и что C4 можно рассматривать как C3. Но при указании границы [C >: C3 <: C2] я ожидаю, что компилятор найдет C, которая учитывает обе границы одновременно, а не одну за другой.

Вопрос: есть ли способ добиться того, чего я хочу, и если нет, то пытается ли компилятор избежать некоторых несоответствий с этим?

Редактировать: из ответов я понял, что мое предположение неверно. C4 всегда соответствует C >: C3, поэтому обе границы действительно соблюдаются. Мой вариант использования — C3 <:< C.


person Chirlo    schedule 24.07.2015    source источник


Ответы (2)


Статически да. Наложить это ограничение довольно просто:

def f[C <: C2](c: C)(implicit ev: C3 <:< C) = 0

f(new C4) теперь не будет компилироваться.

Проблема в том, что, вероятно, невозможно запретить следующее поведение во время компиляции:

val c: C3 = new C4
f(c)

Здесь переменная c имеет статический тип C3, который проходит любую проверку типов компилятором, но на самом деле это C4 во время выполнения.

Во время выполнения вы, конечно, можете проверить тип, используя отражение или полиморфизм, и выдать ошибки или вернуть Failure(...) или None.

person Kolmar    schedule 24.07.2015

Я нашел это объяснение из другого вопроса о стеке по потоку очень полезным:

S >: T просто означает, что если вы передаете тип S, который равен T или его родителю, то будет использоваться S. Если вы передадите тип, который является подуровнем для T, тогда T будет использоваться.

Так что в вашем примере все, но сначала надо скомпилировать. Следующий пример иллюстрирует значение этого: Давайте переопределим f:

def f[U >: C3 <: C2](c: U) = c

а потом:

 val a2 = f(new C2)
 val a3 = f(new C3) 
 val a4 = f(new C4) 
 List[C2](a2, a3, a4)  //compiles
 List[C3](a3, a4)  //compiles
 List[C4](a4)  //does not cause a4 is C3

Надеюсь, это поможет.

person I See Voices    schedule 24.07.2015