2015-08-09 1 views
15

Скажем, у меня есть пустой маркер черты имени Marker и некоторые функции с параметрами типа связанными Marker:Почему эта функция Scala компилируется, когда аргумент не соответствует типу ограничения?

trait Marker 

object Marker { 
    def works[M <: Marker](m:M):M = m 
    def doesntWork[M <: Marker](f:M => String):String = "doesn't matter" 
} 

Первая функция работает, как я ожидал. То есть, если вы передаете параметр, который не является Marker, то код не компилируется:

scala> works("a string") 
<console>:14: error: inferred type arguments [String] do not conform to method works's type parameter bounds [M <: com.joescii.Marker] 
     works("a string") 
    ^
<console>:14: error: type mismatch; 
found : String("a string") 
required: M 
     works("a string") 
      ^

Однако, я могу передать параметр для второй функции, которая не соответствует Marker. В частности, я могу передать функцию типа String => String и код счастливо компилируется и работает:

scala> doesntWork((str:String) => "a string") 
res1: String = doesn't matter 

Я ожидал бы этот вызов doesntWork, чтобы не компилировать. Может ли кто-нибудь объяснить мне, почему он компилируется и как я могу изменить подпись функции, чтобы предотвратить проверку типов в таких случаях?

Полное раскрытие информации: вышеупомянутый надуманный пример представляет собой упрощенную версию this outstanding issue for lift-ng.

ответ

4

M => String на самом деле Function1[M, String]. Если вы посмотрите на определение:

trait Function1[-T1, +R] 

Так M становится контравариантен, что означает, что для M1 >: M2, Function1[M1, String] <: Function1[M2, String], скажем M1 = Any затем Function1[Any, String] <: Function1[Marker, String].

И вход doesntWork - f также контравариантен, что означает, что вы можете передать что-то меньшее, чем M => String, и как я только что показал, Any => String меньше Marker => String, так что passess полностью нормально.

Вы также можете передать String => String из-за твоей [M <: Marker], что в конечном итоге вызывает компилятор интерпретировать M как Nothing, так что даже String => String становится больше, чем M => String.


Чтобы решить проблему, просто ввести обертку, которая сделает ваш тип инвариант:

scala> case class F[M](f: M => String) 
defined class F 

scala> def doesntWork[M <: Marker](f:F[M]):String = "doesn't matter" 
doesntWork: [M <: Marker](f: F[M])String 

scala> doesntWork(F((str: String) => "a string")) 
<console>:18: error: inferred type arguments [String] do not conform to method doesntWork's type parameter bounds [M <: Marker] 
       doesntWork(F((str: String) => "a string")) 
      ^
<console>:18: error: type mismatch; 
found : F[String] 
required: F[M] 
       doesntWork(F((str: String) => "a string")) 
         ^
scala> doesntWork(F((str: Any) => "a string")) 
<console>:18: error: inferred type arguments [Any] do not conform to method doesntWork's type parameter bounds [M <: Marker] 
       doesntWork(F((str: Any) => "a string")) 
      ^
<console>:18: error: type mismatch; 
found : F[Any] 
required: F[M] 
Note: Any >: M, but class F is invariant in type M. 
You may wish to define M as -M instead. (SLS 4.5) 
       doesntWork(F((str: Any) => "a string")) 

scala> doesntWork(F((str: Marker) => "a string")) 
res21: String = doesn't matter 

scala> trait Marker2 extends Marker 
defined trait Marker2 

scala> doesntWork(F((str: Marker) => "a string")) 
res22: String = doesn't matter 

scala> doesntWork(F((str: Marker2) => "a string")) 
res23: String = doesn't matter 

Это, как правило, плохо рекомендовать такие неявные преобразования, но здесь, кажется, в порядке (если вы не будете злоупотреблять F):

scala> implicit def wrap[M](f: M => String) = F(f) 
warning: there was one feature warning; re-run with -feature for details 
wrap: [M](f: M => String)F[M] 

scala> doesntWork((str: Marker) => "a string") 
res27: String = doesn't matter 

scala> doesntWork((str: String) => "a string") 
<console>:21: error: inferred type arguments [String] do not conform to method doesntWork's type parameter bounds [M <: Marker] 
       doesntWork((str: String) => "a string") 
      ^
<console>:21: error: type mismatch; 
found : F[String] 
required: F[M] 
       doesntWork((str: String) => "a string") 
            ^
+0

Но OP проходит '(str: String) =>« строка »' как аргумент - это не тип 'Any => String' (и любой подтип этого)? – chi

+0

это неправильно, проверьте ответ @ larsrh – kosii

+0

Это все еще полезно. Я не думал о том, что 'M => String' на самом деле' Function1 [M, String] ', который мог бы отключить мой ментальный флаг для рассмотрения контравариантности. Я знаком с концепцией, но я, конечно, все еще не знаю. :) – joescii

6

Код компилируется из-за контравариантности. Вы можете видеть, что, явно указывая параметр выводимого типа:

doesntWork[Nothing]((str: String) => "a string") 

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

+1

Спасибо, что ответили здесь и чирикали на меня. Кажется, мне просто нужно было объявить его как 'doesntWork [M>: Marker] ...'. – joescii

+0

Я стою исправлены. Переворачивание углового кронштейна НЕ является волшебным трюком. Это приводит к сбою даже действительной функции. Например, если я создаю 'case class Container (str: String), то расширяет Marker', этот действительный случай не компилируется:' doesntWork ((obj: Container) => "stuff") ' – joescii

+0

@joescii Расширенное использование дженериков вызвало у меня ничего, кроме скорби в прошлом, но как только вы испытаете это, я могу только надеяться, что он поправится. – mucaho

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