Почему функция Scala компилируется, когда аргумент не соответствует типу ограничения?

Скажем, у меня есть пустая маркерная черта с именем Marker и некоторые функции с параметрами типа, привязанными к Marker:

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 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 не скомпилируется. Может ли кто-нибудь объяснить мне, почему он компилируется и как я могу изменить подпись функции, чтобы предотвратить проверку типов в таких случаях?

Полное раскрытие: вышеупомянутый надуманный пример - упрощенная версия этой выдающейся проблемы для лифта.

Ответы

Ответ 1

M => String на самом деле a 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, поэтому он пассисс полностью прекрасен.

Вы также можете передать 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 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 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 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")
                                       ^

Ответ 2

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

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

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