开发者

Scala abstract path dependent type problem part 2

Couple of questions on scala abstract types.

  1. 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.
  2. 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
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜