Scala abstract path dependent type problem part 2
Couple of questions on scala abstract types.
- Do I have to use parameterized [] types if I want to use the type in a constructor value? ie. is it possible to have a class with abstract constructor parameter types? If I get rid of [T1,T2] and use INode#TKey and INode#TValue as the constructor parameter types, what am I doing? I get confusing error messages.
- How do I solve this nicely without resorting to inner types? My use of INode in the definitions seem to be implying I could return / receive an INode with different types for TKey & TValue. How do I restrict it to the same TKey/TValue types as my current type, without restricting myself to returning/receiving exactly "this" instance?
trait AbsTypes
{
type TKey
type TValue
}
trait INode extends AbsTypes
{
def get(key : TKey) : TValue
def set(key : TKey, v : TValue) : INode
def combine(other : INode, key : TKey): INode
}
class ANode[T1,T2](
val akey : T1,
val aval : T2
) extends INode
{
type开发者_开发技巧 TKey = T1
type TValue = T2
type Node = ANode[T1,T2]
def get(key : TKey) : TValue = { aval }
def set(key : TKey, v : TValue) : INode = {
new ANode(key,v)
}
def combine(other : INode, key :TKey) : INode = {
//ERROR : type mismatch; found : key.type (with underlying type ANode.this.TKey) required: other.TKey
other.set(key, aval)
}
}
I think parameterized types fit nicely in your setting. Abstract types are bound to a particular instance, hence the error you are getting. I confess I didn't try to refactor your example to make abstract types work, but the following snippet doesn't try to mix abstract and parameterized types, and scalac has no problems with it.
trait INode[TKey, TValue]
{
type Node = INode[TKey, TValue]
def get(key : TKey) : TValue
def set(key : TKey, v : TValue) : Node
def combine(other : Node, key : TKey): Node
}
class ANode[TKey,TValue](
val akey : TKey,
val aval : TValue
) extends INode[TKey, TValue]
{
def get(key : TKey) : TValue = aval
def set(key : TKey, v : TValue) : Node = new ANode(key,v)
def combine(other : Node, key : TKey) : Node = other.set(key, aval)
}
First of all I would like to point out that the combine
method does not use the other
parameter at all, you could just as well write:
def combine(key : TKey) : Node = set(key, aval)
.. but I guess it is just the principle we are after here.
If you have a very small set of allowed TKey and TValue combinations (say 2), you can use the abstract type tactics and cut parameterized types out of the code all together:
trait INode
{
type TKey
type TValue
def get(key : TKey) : TValue
def set(key : TKey, v : TValue) : INode
def combine(other : INode, key : TKey): INode
}
case class StringNode(
val m_key : String,
val m_val : String
) extends INode
{
type TKey = String
type TValue = String
override def get(key : TKey) : TValue = { m_val }
override def set(key : TKey, v : TValue) : INode = new StringNode(key,v)
override def combine(other : INode, key :TValue): INode = {
other match {
case node: StringNode => node.set(key, m_val)
case _ => throw new IllegalArgumentException("Not OK bla bla")
}
}
}
In my example you will get some code duplication in the methods of StringNode
and, say, IntNode
, but at least the principle of abstract types is illustrated.
Since other
is a different INode
, it has it's own TKey
and TValue
types. There is no guarantee that TKey
and TValue
of this ANode
match those of other
. You need to constrain the types with either equality or lower bound (which I used). I didn't try to run it, but the following compiles against Scala 2.8.0
trait AbsTypes {
type TKey
type TValue
}
trait INode extends AbsTypes {
def get(key : TKey) : TValue
def set(key : TKey, v : TValue) : INode
//def combine(other : INode, key : TKey): INode
type TNode = INode {
type TKey >: INode.this.TKey
type TValue >: INode.this.TValue
}
def combine(other : TNode, key : TKey) : INode
}
class ANode[T1,T2](val akey : T1, val aval : T2) extends INode {
type TKey = T1
type TValue = T2
type Node = ANode[T1,T2]
def get(key : TKey) : TValue = { aval }
def set(key : TKey, v : TValue) : INode = {
new ANode(key,v)
}
def combine(other : TNode, key : TKey) : INode = {
other.set(key, aval)
}
}
Thanks for your answers, they're most helpful & have really helped my understanding. One alternative solution I've found is below. The AbsType doesn't need to be the base class, you can use it to wrap up the parameterized type definitions.
class ANode[T <: AbsTypes](
val akey : T#TKey,
val aval : T#TValue
) extends INode[T]
{
def get(key : TKey) : TValue = { aval }
def set(key : TKey, v : TValue) : Node = {
new ANode[T](key,v)
}
def combine(other : Node, key : TKey) : Node = {
other.set(key, aval) // "use" this & other somehow
}
}
// Examples
class AbsTypeDef[TKey1, TValue1] extends AbsTypes
{
type TKey = TKey1
type TValue = TValue1
}
object ANode
{
type AIntString = AbsTypeDef[Int,String]
type AStringLong = AbsTypes { type TKey = String; type TValue = Long}
def n1 = new ANode[AIntString](1,"one")
def n1b = new ANode[AIntString](2,"two")
def n2 = new ANode[AStringLong]("two",2L)
n1.combine(n1b,2)
//n1.combine(n2,2) // compile error
}
精彩评论