marcin_koss
marcin_koss

Reputation: 5882

How to work with abstract type members in Scala

I'm running into issues with the following code (heavily simplified). Looks like the problem may be with the way I use abstract type members. I would appreciate someone pointing out and explaining what I'm doing wrong here. Compiler error at the bottom. I'm using Scala version 2.12.

trait DataType {
    type A

    def convert(value: String): A
    def convertToString(value: A): String
}

case object IntType extends DataType {
    type A = Int

    def convert(value: String): A = value.toInt
    def convertToString(value: A): String = value.toString
}

trait Codec[T <: DataType] {
    val dtype: T

    def encode(data: Array[String]): Array[T#A]
    def decode(data: Array[T#A]): Array[String]
}

class CodecImp[T <: DataType](val dtype: T)(implicit tag: ClassTag[T#A]) extends Codec[T] {
    def encode(data: Array[String]): Array[T#A] = {
        Array[T#A](dtype.convert(data(0)))
    }

    def decode(data: Array[T#A]): Array[String] = {
        Array[String](dtype.convertToString(data(0)))
    }
}

val cod = new CodecImp(IntType)
val encoded = cod.encode(Array("1", "2", "3")) // expecting: Array[IntType.A]
val decoded = cod.decode(encoded) // expecting: Array[String]

Compiler error.

Error:(30, 50) type mismatch;
 found   : T#A
 required: CodecImp.this.dtype.A
        Array[String](dtype.convertToString(data(0)))                
                 ^

Upvotes: 0

Views: 1301

Answers (3)

Dan M
Dan M

Reputation: 111

I found What does the # operator mean in Scala? explained the '#' operator pretty well.
Each instance of DataType has it's own path dependent type A.
The difference between: T#A meaning A is a nested class of any T and dtype.A meaning the A class of dtype

You could change the Codec trait method signatures to something like:

def encode(data: Array[String]): Array[dtype.A]
def decode(data: Array[dtype.A]): Array[String]

But type parameters might be a better way to express the relation.

Upvotes: 2

chengpohi
chengpohi

Reputation: 14217

This is caused by dtype.convertToString(data(0)) this method is wanting a variable with type: dtype#A, this type is decided by the variable dtype, but data type is Array[T#A], so this caused type mismatch, it's can't resolve in Scala, Since we can't state our method, like: def decode(data: Array[dType.A])... that's compiler expected.

And You can solve this by generics type agains type alias, like:

trait DataType[T] {
  def convert(value: String): T
  def convertToString(value: T): String
}

case object IntType extends DataType[Int] {

  def convert(value: String): Int = value.toInt
  def convertToString(value: Int): String = value.toString
}


class CodecImp[B](val dtype: DataType[B])(implicit tag: ClassTag[B]) {
  def encode(data: Array[String]): Array[B] = {
    Array[B](dtype.convert(data(0)))
  }

  def decode(data: Array[B]): Array[String] = {
    Array[String](dtype.convertToString(data(0)))
  }
}

Upvotes: 1

Dima
Dima

Reputation: 40500

The thing is that each instance of DataType can have A defined as anything whatsoever. Consider this:

class Foo extends DataType {
   type A = Int
   def convertToString(i: Int) = i.toString
   def convert(s: String) = s.toInt
}

class Bar extends DataType {
    type A = String
    def convertToString(s: String) = s
    def convert(s: String) = s
 }

Now, if I do val codecs = Seq(CodecImpl(new Foo, new Bar)

and if that compiles, then what type should the argument be to codecs.map(_.decode(whateverMakesSenseHere))?

This doesn't work ... You can't use T#A like this, because it is abstract.

I get a feeling, you'd be better off modeling what you need to model with type parameters rather than path dependent types. This just doesn't really seem like a use case for the latter.

Upvotes: 0

Related Questions