Контравариантность и val

Как и почему «val» и «case» влияют на систему типов? (особенно дисперсия)

Welcome to Scala version 2.8.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_22).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class E[-A]
defined class E

scala> class F[-A](val f: E[A] => Unit)
<console>:6: error: contravariant type A occurs in covariant position in type => (E[A]) => Unit of value f
class F[-A](val f: E[A] => Unit)
                       ^  
scala> case class C[-A](f: E[A] => Unit)
<console>:6: error: contravariant type A occurs in covariant position in type => (E[A]) => Unit of value f
   case class C[-A](f: E[A] => Unit)

scala> class F[-A](f: E[A] => Unit)    
defined class F

person ladrl    schedule 23.02.2011    source источник


Ответы (4)


Учти это:

trait Equal[-A] { def eq(a1: A, a2: A): Boolean }
val e = new Equal[Option[Int]] { 
    def eq(a1: Option[Int], a2: Option[Int]) = a1 forall (x => a2 forall (x ==)) 
}

// Because Equal is contra-variant, Equal[AnyRef] is a subtype of Equal[String]
// Because T => R is contra-variant in T, Equal[AnyRef] => Unit is a supertype
// of Equal[String] => Unit
// So the follow assignment is valid
val f: Equal[AnyRef] => Unit = (e1: Equal[String]) => println(e1.eq("abc", "def"))


// f(e) doesn't compile because of contra-variance
// as Equal[Option[Int]] is not a subtype of Equal[AnyRef]

// Now let's tell Scala we know what we are doing
class F[-A](val f: Equal[A @uncheckedVariance] => Unit)

// And then let's prove we are not:
// Because F is contra-variant, F[Option[Int]] is a subtype of F[AnyRef]
val g: F[Option[Int]] = new F(f)

// And since g.f is Equal[Option[Int]] => Unit, we can pass e to it.
g.f(e) // compiles, throws exception

Если f не виден снаружи F, этой проблемы быть не может.

person Daniel C. Sobral    schedule 24.02.2011
comment
Итак, это означает, что решающим фактором в моем примере является то, что val и case генерируют открытые члены для аргумента конструктора? - person ladrl; 24.02.2011
comment
С точки зрения типизации вы можете думать о своем коде как о class E[-A] { def f : A = ... }, который помещает A в ковариантную позицию. - person James Iry; 24.02.2011

Вы спрашиваете, что такое дисперсия? Если вы знаете, что такое дисперсия, это говорит само за себя. В примере без «val» или «case» нет видимых извне элементов, включающих A, поэтому он не может вызвать ошибку дисперсии.

person psp    schedule 23.02.2011
comment
Пол, это дерьмовый ответ: если бы ты был не тебой, это было бы минусом! - person oxbow_lakes; 24.02.2011
comment
Ответ намеренно указывает на вопрос. - person psp; 24.02.2011

«val» означает, что поле видно извне. Рассмотреть возможность:

val f: E[Any] => Unit = { ... }
val broken: F[Int] = new F[Any](f) // allowed by -A annotation
val f2: E[Int] => Unit = broken.f // must work (types match)
val f3: E[Int] => Unit = f // type error

По сути, нам удалось небезопасно вызвать f, не действуя явным образом. Это работает только в том случае, если f виден, т.е. если вы определяете его как val или используете класс case.

person qmajor    schedule 24.02.2011

Вот контравариантный «выходной канал», который просто выводит на консоль:

class OutputChannel[-T] {   
  def write(t:T) = println(t); 
}

Вот он в действии:

val out:OutputChannel[Any] = new OutputChannel[Any]
out.write(5)

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

val out2:OutputChannel[String] = out
out2.write("five")
out2.write(55) //wont compile

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

//!!! as you've seen code like this won't compile w/ contravariant types!!!!
class OutputChannel[-T] {   
  var history:List[T] = Nil
  def write(t:T) = { 
    history = history :+ t;  
    println(t); 
  } 
}

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

//history(0) is an Int - runtime exception (if scala allowed it to compile)
val firstStringOutputted:String = out2.history(0) 

Поскольку контравариантность допускает это «сужение» типов (т. е. здесь от Any до String), система типов не может выставлять значения типа T, такие как это поле «истории», которое я сделал, или поле «f», которое у вас было.

Другими известными «противниками» являются функции и компараторы:

val strHashCode:String => Int = { s:Any => s.hashCode }  //function which works with any object
val strComp:Comparator<String> = new HashCodeComparator()   //comparator object which works with any object
person Adam Rabung    schedule 24.02.2011