2015-11-12 3 views
7

Столкнулся это странное поведение при изменении верхней границы в реализации, но забыл его изменить в интерфейсе. Я думаю, что последний оператор не должен компилироваться, но он делает и возвращает неожиданный результат.Странное поведение типа вывода в функции с верхней границей

trait SuperBase 
trait Base extends SuperBase 

class SuperBaseImpl extends SuperBase 

trait Service { 
    def doWork[T <: Base : Manifest](body: T => Unit): String 
    def print[T <: Base : Manifest]: String 
} 

object ServiceImpl extends Service { 
    override def doWork[T <: SuperBase : Manifest](body: T => Unit): String = 
    print[T] 
    def print[T <: SuperBase : Manifest]: String = 
    manifest[T].runtimeClass.toString 
} 

val s: Service = ServiceImpl 

// does not compile as expected 
// s.print[SuperBaseImpl] 

// returns "interface Base" 
s.doWork { x: SuperBaseImpl =>() } 

Редактировать

Как @ сома-snytt упоминается с -Xprint:typer опции мы можем увидеть, что на самом деле компилятор выводит:

s.doWork[Base with SuperBaseImpl] 

Это объясняет, почему мы получаем "интерфейс Base". Но я до сих пор не совсем понимаю, как и зачем работать с типом в этом случае.

ответ

1

Обратите внимание, что то, что ваш код высказывание:

Метод ServeImp.doWork должен принимать параметр, который является «функция, которая должна принять некоторый класс T, который является sublass из базы и сверхоснование»

SuperBaseImpl не является подкласс базы, но это не ошибка, потому что может существовать класс X, который «расширяет SuperBaseImpl с базой», которая будет удовлетворять это требование.

Когда умозаключение типа происходит, T разрешен к «foo.Base с foo.SuperBaseImpl», которая удовлетворяет всем требованиям, указанным выше. runtimeClass - это интерфейс Base, потому что нет способа описать этот тип в JVM во время выполнения, но если вы выполните manifest.toString - вы увидите правильный тип.

Там нет реального способа продемонстрировать, что с вашим примером, но необходимо учитывать следующее:

trait SuperBase 
trait Base extends SuperBase 

class SuperBaseImpl(val a: String) extends SuperBase 

trait Service { 
    def doWork[T <: Base : Manifest](body: T => String): (T) => String 
} 

object ServiceImpl extends Service { 
    override def doWork[T <: SuperBase : Manifest](body: T => String): (T) => String = 
    x => "Manifest is '%s', body returned '%s'".format(manifest[T].toString(), body(x)) 
} 

val s: Service = ServiceImpl 

val f = s.doWork { x: SuperBaseImpl => x.a } 
// f: Base with SuperBaseImpl => String = <function1> 

f(new SuperBaseImpl("foo") with Base) 
// res0: String = Manifest is 'Base with SuperBaseImpl', body returned 'foo' 

f(new SuperBaseImpl("foo")) 
// compile error 

Здесь я сделал DoWork вернуть другую функцию, которая принимает T, и вы можете увидеть, что он решил, и что вы можете называть это, и он будет работать правильно, если вы передадите что-то, что соответствует ограничениям для всех типов.

Добавлено:

Также обратите внимание, что ваша иерархия классов не является необходимым, чтобы показать, что поведение на всех, они могут быть совершенно не связаны.

trait A 
trait B 

def m[T <: A : Manifest](body: T => Unit) = manifest[T].toString() 

m((x: B) => Unit) 
//res0: String = A with B 
1

Это выглядит странно, но чувствует звук. Обратите внимание, что вы также можете позвонить

s.doWork { x: Any =>() } 

Я просто думаю, что параметр типа T каким-то образом «необитаемый». Метод ничего не знает о T, кроме его верхней границы Base, поэтому вы получите манифест для Base. Но опять же с этим вы не можете многое сделать, потому что это не может построить значение типа T ... Так что все остается звуковым.

Попробуйте изменить подпись

def doWork[T <: Base : Manifest](x: T)(body: T => Unit): String 

Тогда вы не можете использовать его таким образом:

s.doWork(123: Int) { x: Any =>() } // no 
s.doWork(123: Any) { x: Any =>() } // no 
+0

Спасибо, но я считаю, что проще просто определить 'T':' s.doWork [SuperBaseImpl] {x =>()} '. –

3

С -Xprint:typer, вы увидите, что компилятор выводит для T:

s.doWork[Base with SuperBaseImpl] 

Что такое оценка пытается выразить? Функции ковариантны в параметре, поэтому вы выражаете, что body должен принять определенный аргумент достаточно узкого типа. Обычно вам требуется, чтобы функция имела дело с широким типом.

Может быть, вы хотели нижнюю границу.

scala> trait SuperBase 
defined trait SuperBase 

scala> trait Base extends SuperBase 
defined trait Base 

scala> class SuperBaseImpl extends SuperBase 
defined class SuperBaseImpl 

scala> trait Service { def f[A >: Base : Manifest](g: A => Unit): String } 
defined trait Service 

scala> object Impl extends Service { def f[A >: Base : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString } 
defined object Impl 

scala> (Impl: Service).f { x: Base =>() } 
res0: String = interface Base 

scala> (Impl: Service).f { x: SuperBase =>() } 
res1: String = interface SuperBase 

scala> (Impl: Service).f { x: SuperBaseImpl =>() } 
<console>:17: error: inferred type arguments [SuperBaseImpl] do not conform to method f's type parameter bounds [A >: Base] 
     (Impl: Service).f { x: SuperBaseImpl =>() } 
        ^
<console>:17: error: type mismatch; 
found : SuperBaseImpl => Unit 
required: A => Unit 
     (Impl: Service).f { x: SuperBaseImpl =>() } 
              ^
<console>:17: error: No Manifest available for A. 
     (Impl: Service).f { x: SuperBaseImpl =>() } 
         ^

scala> object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString } 
<console>:14: error: overriding method f in trait Service of type [A >: Base](g: A => Unit)(implicit evidence$1: Manifest[A])String; 
method f has incompatible type 
     object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString } 
             ^
+0

Спасибо за трюк с '-Xprint: typer', он делает все для меня более понятным. Но я до сих пор не понимаю, как и почему он работает таким образом. Это ошибка? О границах, верхняя граница была использована для вызова некоторых методов, определенных в Base/SuperBase до/после вызова функции аргумента. И затем он был изменен в Impl только по ошибке. –

Смежные вопросы