继承和(自动?)类型转换

Agl*_*Agl 7 scala

请看一下下面的代码,其中Extractor[A,B]是通用框架的一部分,其他一切应该被视为"客户端代码"(我把它煮了很多,并重命名为所有内容.所以不要介意Extractor似乎没有太有用了).

scala> abstract class Extractor[A,B] {                                          
     |   def extract(d:A):B                                                     
     |   def stringRepr(d:A):String                                             
     | }                                                                        
defined class Extractor

scala> sealed abstract class Value                                              
defined class Value

scala> case class IntValue(i:Int) extends Value                                 
defined class IntValue

scala> case class StringValue(s:String) extends Value                           
defined class StringValue

scala> case class Data(i:Int, s:String)                                         
defined class Data

scala> sealed abstract class MyExtractor[Value] extends Extractor[Data, Value] {
     |   def stringRepr(d:Data) = extract(d) match {                            
     |     case IntValue(i) => i.toString                                       
     |     case StringValue(s) => s                                             
     |   }                                                                      
     | }                                                                        
defined class MyExtractor

scala> class IntExtractor(name:String) extends MyExtractor[IntValue] {
     |   def extract(d:Data) = IntValue(d.i)
     | }
defined class IntExtractor

scala> class StringExtractor(name:String) extends MyExtractor[StringValue] {
     |   def extract(d:Data) = StringValue(d.s)
     | }
defined class StringExtractor
Run Code Online (Sandbox Code Playgroud)

因此,简短的单词Extractor[A,B]用于从中显示一些值B,A并执行本节目代码中未表示的其他一些内容.抽象类ValueMyExtractor用于"客户端代码"中类型savety的原因.当我尝试创建ListMyExtractorS,会发生以下情况:

scala> val l = List.empty[MyExtractor[Value]]
l: List[MyExtractor[Value]] = List()

scala> new IntExtractor("test1") :: l
res5: List[MyExtractor[_ >: IntValue <: Value]] = List(IntExtractor@1fd96c5)
Run Code Online (Sandbox Code Playgroud)

试图将一个转换IntExractor为一个超类

scala> new IntExtractor("test"):MyExtractor[Value]   
<console>:24: error: type mismatch;
 found   : IntExtractor
 required: MyExtractor[Value]
       new IntExtractor("test"):MyExtractor[Value]
       ^

scala> new IntExtractor("test"):Extractor[Data,Value]
<console>:24: error: type mismatch;
 found   : IntExtractor
 required: Extractor[Data,Value]
       new IntExtractor("test"):Extractor[Data,Value]
Run Code Online (Sandbox Code Playgroud)

我知道,一切都很好,当我确定IntExtractor这样的

scala> class IntExtractor(name:String) extends MyExtractor[Value] {
     |   def extract(d:Data) = IntValue(d.i)                            
     | }
defined class IntExtractor

scala> new IntExtractor("test"):Extractor[Data,Value]              
res17: Extractor[Data,Value] = IntExtractor@1653d7a
Run Code Online (Sandbox Code Playgroud)

但我不明白,为什么它不能像我上面尝试的那样工作.我会感谢任何帮助或提示.

Dav*_*ith 7

尽管我可以说,你正在寻找的概念是"协方差".仅仅因为IntValue是子类型Value并不意味着它MyExtractor[IntValue]是一个子类型MyExtractor[Value].默认情况下,这两种类型之间根本没有子类型关系.要创建这样的关系,您需要声明MyExtractor与其参数的协变.Scala允许您通过在类型参数声明之前添加"+"来声明类型参数是协变的.这称为方差表示法.

sealed abstract class MyExtractor[+Value] extends Extractor[Data, Value] {        
}   
Run Code Online (Sandbox Code Playgroud)

Scala还支持类型参数的逆变.逆变量就像协方差,但是相反,并且在类型参数上用" - "方差表示法表示.你的Extractor类型提供了一个很好的例子,说明了逆变符号是有意义的.

abstract class Extractor[-A,+B] {                                          
   def extract(d:A):B                                                     
   def stringRepr(d:A):String                                             
}       
Run Code Online (Sandbox Code Playgroud)

这意味着如果Foo是子类型Bar,那么它Extractor[Bar, Baz]是一个子类型Extractor[Foo, Baz],如果你认为它是有道理的.如果某些东西可以在传递超类型的实例时提取所需的数据,那么根据定义它可以在传递子类型的实例时提取它.相反,如果Foo是子类型Bar,那么Extractor[Baz, Foo]是子类型Extractor[Baz, Bar].这也是有道理的.如果你有一个返回a的提取器Foo,你当然可以在任何需要返回a的提取器的地方使用它Bar.

何时可以宣布违反和协方差的限制.例如,逆变类型参数只能用作方法参数,协变参数只能用作方法返回值或val.两者都不能用作变量.嵌套类型参数会变得更复杂,但规则基本上归结为"合理的地方",并且您的示例符合所有规则.

另外注意,您示例中的所有抽象类都应该被声明为traits.只要您的抽象类不需要构造函数参数,将它们声明为特征就可以为您提供更多重用机会.