После обсуждений в списке рассылки Scala - большое спасибо людям за то, что они поставили меня на правильный путь! - Я думаю, что это самое близкое, что можно прийти к минимальным рамкам. Я оставляю это здесь для справки, и я использую другой пример, потому что он подчеркивает, что происходит лучше:
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
}
}
Ключевым моментом здесь является добавление параметра типа MyType
, который является заполнителем для типа класса, с которым мы действительно закончим. Каждый раз, когда мы наследуем, мы должны переопределять его как параметр типа, и мы должны добавить метод конструктора, который создаст новый объект этого типа. Если конструктор изменится, мы должны создать новый метод конструктора.
Теперь, когда вы хотите создать класс для фактического использования, вам нужно только заполнить метод конструктора вызовом new (и сообщить классу, что он имеет свой собственный тип):
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)
}
и все готово:
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
Итак, чтобы рассмотреть, минимальный шаблон - если вы хотите включить рекурсивные методы, которые нарушают другой стиль ответа - предназначен для любых методов, которые возвращают новую копию извне класса (используя new или factory или что угодно), чтобы оставить его абстрактным (здесь я свел все к одному методу, который дублирует конструктор), и вы должны добавить аннотацию типа MyType
, как показано. Затем, на последнем шаге, необходимо создать новые методы копирования.
Эта стратегия прекрасно работает и для ковариации в A
, за исключением того, что этот конкретный пример не работает, поскольку f
и g
не являются ковариантными.