Minimal framework in Scala for collections with inheriting return type

后端 未结 3 2289
旧时难觅i
旧时难觅i 2021-02-19 04:48

Suppose one wants to build a novel generic class, Novel[A]. This class will contain lots of useful methods--perhaps it is a type of collection--and therefore you w

相关标签:
3条回答
  • 2021-02-19 04:53

    After discussions on the Scala mailing list--many thanks to the people there for setting me on the right track!--I think that this is the closest that one can come to a minimal framework. I leave it here for reference, and I'm using a different example because it highlights what is going on better:

    abstract class Peano[A,MyType <: Peano[A,MyType]](a: A, f: A=>A) {
      self: MyType =>
      def newPeano(a: A, f: A=>A): MyType
      def succ: MyType = newPeano(f(a),f)
      def count(n: Int): MyType = {
        if (n<1) this
        else if (n==1) succ
        else count(n-1).succ
      }
      def value = a
    }
    
    abstract class Peano2[A,MyType <: Peano2[A,MyType]](a: A, f: A=>A, g: A=>A) extends Peano[A,MyType](a,f) {
      self: MyType =>
      def newPeano2(a: A, f: A=>A, g: A=>A): MyType
      def newPeano(a: A, f: A=>A): MyType = newPeano2(a,f,g)
      def pred: MyType = newPeano2(g(a),f,g)
      def uncount(n: Int): MyType = {
        if (n < 1) this
        else if (n==1) pred
        else uncount(n-1).pred
      }
    }
    

    The key here is the addition of the MyType type parameter that is a placeholder for the type of the class that we'll really end up with. Each time we inherit, we have to redefine it as a type parameter, and we have add a constructor method that will create a new object of this type. If the constructor changes, we have to create a new constructor method.

    Now when you want to create a class to actually use, you only have to fill in the constructor method with a call to new (and tell the class that it's of its own type):

    class Peano2Impl[A](a: A, f: A=>A, g: A=>A) extends Peano2[A,Peano2Impl[A]](a,f,g) {
      def newPeano2(a: A, f: A=>A, g: A=>A) = new Peano2Impl[A](a,f,g)
    }
    

    and you're off and running:

    val p = new Peano2Impl(0L , (x:Long)=>x+1 , (y:Long)=>x-1)
    
    scala> p.succ.value
    res0: Long = 1
    
    scala> p.pred.value
    res1: Long = -1
    
    scala> p.count(15).uncount(7).value
    res2: Long = 8
    

    So, to review, the minimal boilerplate--if you want to include recursive methods, which breaks the other style of answer--is for any methods that return a new copy from outside the class (using new or a factory or whatever) to be left abstract (here, I've boiled everything down to one method that duplicates the constructor), and you have to add the MyType type annotation as shown. Then, at the final step, these new-copy methods have to be instantiated.

    This strategy works fine for covariance in A also, except that this particular example doesn't work since f and g are not covariant.

    0 讨论(0)
  • 2021-02-19 05:06

    I haven't thought this through fully, but it type checks:

    object invariant {
      trait Novel[A] {
        type Repr[X] <: Novel[X]
    
        def reverse: Repr[A]
    
        def revrev: Repr[A]#Repr[A]
           = reverse.reverse
      }
      class ShortStory[A] extends Novel[A] {
        type Repr[X] = ShortStory[X]
    
        def reverse = this
      }
    
      val ss = new ShortStory[String]
      val ss2: ShortStory[String] = ss.revrev
    }
    
    object covariant {
      trait Novel[+A] {
        type Repr[X] <: Novel[_ <: X]
    
        def reverse: Repr[_ <: A]
    
        def revrev: Repr[_ <: A]#Repr[_ <: A] = reverse.reverse
      }
    
      class ShortStory[+A] extends Novel[A] {
        type Repr[X] = ShortStory[X]
    
        def reverse = this
      }
    
      val ss = new ShortStory[String]
      val ss2: ShortStory[String] = ss.revrev
    }
    

    EDIT

    The co-variant version can be much nicer:

    object covariant2 {
      trait Novel[+A] {
        type Repr[+X] <: Novel[X]
    
        def reverse: Repr[A]
    
        def revrev: Repr[A]#Repr[A] = reverse.reverse
      }
    
      class ShortStory[+A] extends Novel[A] {
        type Repr[+X] = ShortStory[X]
    
        def reverse = this
      }
    
      val ss = new ShortStory[String]
      val ss2: ShortStory[String] = ss.revrev
    }
    
    0 讨论(0)
  • 2021-02-19 05:07

    Edit: I just realized that Rex had a concrete class Novel in his example, not a trait as I've used below. The trait implementation is a bit too simple to be a solution to Rex's question, therefore. It can be done as well using a concrete class (see below), but the only way I could make that work is by some casting, which makes this not really 'compile time type-safe'. This So this does not qualify as a solution.

    Perhaps not the prettiest, but a simple example using abstract member types could be implemented as follows:

    
    trait Novel[A] { 
       type T <: Novel[A] 
       def reverse : T 
       def revrev : T#T = reverse.reverse 
    }
    
    class ShortStory[A](var story: String) extends Novel[A] {
     type T = ShortStory[A]
     def reverse : T = new ShortStory[A](story reverse)
     def myMethod: Unit = println("a short story method")
    }
    
    scala> val ss1 = new ShortStory[String]("the story so far")
    ss1: ShortStory[String] = ShortStory@5debf305
    
    scala> val ssRev = ss1 reverse 
    ssRev: ss1.T = ShortStory@5ae9581b
    
    scala> ssRev story
    res0: String = raf os yrots eht
    
    scala> val ssRevRev = ss1 revrev
    ssRevRev: ss1.T#T = ShortStory@2429de03
    
    scala> ssRevRev story
    res1: String = the story so far
    
    scala> ssRevRev myMethod
    a short story method
    

    It's certainly minimal, but I doubt whether this would enough to be used as a kind of framework. And of course the types returned not anywhere near as clear as in the Scala collections framework, so perhaps this might be a bit too simple. For the given case, it seems to do the job, however. As remarked above, this does not do the job for the given case, so some other solution is required here.

    Yet Another Edit: Something similar can be done using a concrete class as well, though that also not suffices to be type safe:

    
    class Novel[A](var story: String) {
      type T <: Novel[A] 
      def reverse: T = new Novel[A](story reverse).asInstanceOf[T]  
      def revrev : T#T = reverse.reverse
    }
    class ShortStory[A](var s: String) extends Novel[A](s) {
     type T = ShortStory[A]
     override def reverse : T = new ShortStory(story reverse)
     def myMethod: Unit = println("a short story method")
    }
    

    And the code will work as in the trait example. But it suffers from the same problem as Rex mentioned in his edit as well. The override on ShortStory is not necessary to make this compile. However, it will fail at runtime if you don't do this and call the reverse method on a ShortStory instance.

    0 讨论(0)
提交回复
热议问题