代码之家  ›  专栏  ›  技术社区  ›  Jean-Philippe Pellet

如何使用scala的这种类型、抽象类型等来实现自我类型?

  •  44
  • Jean-Philippe Pellet  · 技术社区  · 14 年前

    我在其他问题上找不到这个答案。假设我有一个抽象超类abstract0,它有两个子类:concrete1和concrete1。我希望能够在abstract0中定义

    def setOption(...): Self = {...}
    

    其中self是具体的子类型。这样可以将调用链接到setoption,如下所示:

    val obj = new Concrete1.setOption(...).setOption(...)
    

    仍然得到具体的1作为obj的推断类型。

    我不想定义这个:

    abstract class Abstract0[T <: Abstract0[T]]
    

    因为它使客户机更难处理这种类型。我尝试了各种可能性,包括抽象类型:

    abstract class Abstract0 {
      type Self <: Abstract0
    }
    
    class Concrete1 extends Abstract0 {
      type Self = Concrete1
    }
    

    但是它不可能实现setoption,因为 this 在abstract0中没有self类型。并使用 this: Self => 在abstract0中也不起作用。

    这个问题有什么解决办法?

    2 回复  |  直到 9 年前
        1
  •  58
  •   sksamuel    9 年前

    这是什么 this.type 是:

    scala> abstract class Abstract0 {
         |   def setOption(j: Int): this.type
         | }
    defined class Abstract0
    
    scala> class Concrete0 extends Abstract0 {
         |   var i: Int = 0
         |   def setOption(j: Int) = {i = j; this}
         | }
    defined class Concrete0
    
    scala> (new Concrete0).setOption(1).setOption(1)
    res72: Concrete0 = Concrete0@a50ea1
    

    如您所见,setoption返回实际使用的类型,而不是abstract0。如果混凝土0 setOtherOption 然后 (new Concrete0).setOption(1).setOtherOption(...) 会工作

    更新:在评论中回答JPP的后续问题(如何返回新实例: 问题中描述的一般方法是正确的方法(使用抽象类型)。但是,对于每个子类,新实例的创建需要是明确的。

    一种方法是:

    abstract class Abstract0 {
      type Self <: Abstract0
    
      var i = 0
    
      def copy(i: Int) : Self
    
      def setOption(j: Int): Self = copy(j)
    }
    
    class Concrete0(i: Int) extends Abstract0 {
      type Self = Concrete0
      def copy(i: Int) = new Concrete0(i)
    }
    

    另一个方法是遵循scala集合库中使用的构建器模式。也就是说,setoption接收一个隐式构建器参数。这样做的好处是,构建新实例可以使用更多的方法,而不仅仅是“复制”,而且可以完成复杂的构建。例如,setSpecialOption可以指定返回实例必须是SpecialConcrete。

    下面是解决方案的示例:

    trait Abstract0Builder[To] {
        def setOption(j: Int)
        def result: To
    }
    
    trait CanBuildAbstract0[From, To] {
      def apply(from: From): Abstract0Builder[To]
    }
    
    
    abstract class Abstract0 {
      type Self <: Abstract0
    
      def self = this.asInstanceOf[Self]
    
      def setOption[To <: Abstract0](j: Int)(implicit cbf: CanBuildAbstract0[Self, To]): To = {
        val builder = cbf(self)
        builder.setOption(j)
        builder.result
      }
    
    }
    
    class Concrete0(i: Int) extends Abstract0 {
      type Self = Concrete0
    }
    
    object Concrete0 {
        implicit def cbf = new CanBuildAbstract0[Concrete0, Concrete0] {
            def apply(from: Concrete0) = new Abstract0Builder[Concrete0] {
               var i = 0
               def setOption(j: Int) = i = j
               def result = new Concrete0(i)
            }
        }
    }
    
    object Main {
        def main(args: Array[String]) {
        val c = new Concrete0(0).setOption(1)
        println("c is " + c.getClass)
        }
    }
    

    更新2: 回复JPP的第二条评论。对于多个层次的嵌套,使用类型参数而不是类型成员,并将abstract0转换为特征:

    trait Abstract0[+Self <: Abstract0[_]] {
      // ...
    }
    
    class Concrete0 extends Abstract0[Concrete0] {
      // ....
    }
    
    class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] {
     // ....
    }
    
        2
  •  4
  •   pedrofurla    14 年前

    这是 this.type . 它应该是:

    def setOption(...): this.type = { 
      // Do stuff ...
      this
    }