用作类型容器的Scala类 - 作为类型参数的单例类型的成员类型

时间:2015-09-06 23:17:37

标签: scala generics path-dependent-type

快速提问:为什么以下内容无法编译:

trait SubtypeOf[+T] {
    type Type <: T
}

def SubtypeOf[T] :SubtypeOf[T] = new SubtypeOf[T] { type Type = T }

case class Owned[+P<:Owner](next :Option[P#R])

class Owner {
    type This = this.type
    val R = SubtypeOf[Owned[This]]
    type R = R.Type

    def next(o :Owned[This]) :Option[R] = o.next
}

结果:

Error:(23, 43) type mismatch;
found   : Option[_2.R.Type forSome { val _2: Owner.this.This }]
(which expands to)  Option[_2.R.Type forSome { val _2: Owner.this.type }]
required: Option[Owner.this.R]
(which expands to)  Option[Owner.this.R.Type]
    def next(o :Owned[This]) :Option[R] = o.next

这当然是我能想出的错误最简化的方法,背后的动机和长期问题如下:

我正在实现一个以json模式给出的协议,该模式由几个彼此嵌套的类组成。尽管该协议是一种标准,但不同的合作伙伴实施了自己的扩展,并且这些合作伙伴的数量也在增长。让我们说标准指定了一个'人'对象,可以按如下方式实现:

case class Person(firstName :String, familyName :String)

典型的扩展类似于以下内容:

class PersonP1(firstName :String, familyName :String, val birthDay :Date)

问题开始显示“人”对象是否嵌入数据树中的各种其他对象中,例如:root.history.transactions.buyer,root.history.transactions.buyer.spouse,root。 company.technicalContact,root.company.businessContact等 - 你明白了。在这种情况下,有两种类似的解决方案:

  1. 仅包含实际扩展数据的子类 - person,并且使用强制转换将所有代码与其一起使用。事实上,实际的业务逻辑隐含地要求某些对象具有特定的子类,而这是不可能强制实施的,也是一个难以记录的地狱。

  2. 子类递归所有引用扩展实体的类,即根路径上的所有对象。实际上,这需要子类大部分/整个模式的很大一部分,通常只需缩小其中一个属性的类型。

  3. 访客模式,略好于1)但仍然不允许将整个数据声明为特定于一个伙伴。

  4. 我选择了解决方案4:

    定义容器类型P,声明要用于给定伙伴的实际类型,并使用P参数化所有实体。所以,而不是

    case class Company(businessContact :Person, technicalContact :Person)
    

    我有

    trait Protocol {
        type Person <: BasePerson
        ...
    }
    
    case class Company[P<:Protocol](businessContact :P#Person, technicalContact :P#Person)
    

    这样做更好,但仍然每个协议都必须提供Protocol声明的所有数十种类型的定义,因为scala不允许覆盖类型声明。

    所以,我试图通过引入上面例子中的协变类型持有者来规避这个限制:

    trait SubtypeOf[+T] { type Type <: T }
    
    trait Protocol {
        type This = this.type
        val Person :SubtypeOf[BasePerson[This]] = new SubtypeOf[BasePerson[This]] { 
            type Type = BasePerson[This]
        }
        type Person = Person.Type
        ....
    }
    
    object ExtendedProtocol extends Protocol {
       override val Person = new SubtypeOf[ExtendedPerson[This]] { type Type = ExtendedPerson[This] }
    }
    

    这正是我想要的:当使用带有具体Protocol类型参数化的实体时,对象的所有成员都正确地显示为特殊的子类,我只需要覆盖实际更改的少数几个。如果我将实体类的所有引用限制为Protocol声明的直接引用的成员类型,那么一切都会好的。在选项[P#Person]或Seq [P#Person]中包装P#Person的任何尝试都会导致帖子开头出错。

    从类型系统的角度来看,有一件事是参数化协议与所有实体类型,但它们太多了。我也尝试了

    的自我参数化技巧
    trait Protocol[P<:Protocol[P]] { this :P =>
        val Person = SubtypeOf[Person[P]]
        type Person = Person.Type
    }
    

    但这打破了P#Person是一个路径依赖类型,因此例如person.address与

    中的ProprietaryProtocol.Address不兼容。
    case class Person[P<:Protocol](address :P#Address) 
    val person :Person[ProprietaryProtocol]
    
    object ProprietaryProtocol extends Protocol { ...
    }
    

0 个答案:

没有答案