Generalized structural type conformance in Scala

2020-02-29 02:58发布

问题:

I'm interested in the problem of conforming a specific type to a more general structural type. Consider the following examples:

trait Sup

trait Sub extends Sup

type General = {
   def contra(o: Sub): Unit
   def co(): Sup
   def defaults(age: Int): Unit
   def defaults2(age: Int): Unit
   def defaults3(first: String): Unit
} 

trait Specific {
   def contra(o: Sup): Unit // doesn't conform
   def co(): Sub // conforms
   def defaults(age: Int, name: String = ""): Unit // doesn't conform
   def defaults2(name: String = "", age: Int = 0): Unit // doesn't conform
   def defaults3(first: String = "", last: String = ""): Unit // doesn't conform
}

In each of the non-conforming cases, a call to the method in General can safely be resolved to the corresponding method in Specific. A more interesting practical example can be found in this question:

trait Versionable[T] {
   self: { def copy(version: Int): T } =>
   val version = 0
   def incrementVersion = copy(version = version + 1)
}

case class Customer(name: String, override val version: Int) 
      extends Versionable[Customer] {
   def changeName(newName: String) = copy(name = newName)
}

Here, the Customer's copy method does not conform to the signature in Versionable's self-type annotation. Note, however, that if the compiler allowed, copy could be invoked just as it is in Versionable.incrementVersion. Clearly, the actual signature of Customer's copy method is too specific for use in Versionable, since it carries the irrelevant knowledge that one can optionally supply a name parameter.

Are there ways to work around these limitations? Are there reasons that such generalized conformance would be a bad idea?

回答1:

One concern is that when you read this code:

self: { def copy(version: Int): T }

you do not expect the name of the parameter to be significant, as it would have to be in this example:

case class Robot(number: Int, override val version: Int)
  extends Versionable[Robot]

EDIT: On another note, regarding the lack of parameter contravariance for methods, you can do:

type General = { val contra: (Sub => Unit) }
class B { val contra = ((o:Sup) => println(o)) }
var b:General = new B