selfsx
selfsx

Reputation: 591

Instance types parametrization in Scala

Coming from Java, I'm trying to develop simple SQL generator, here's bare bones of this code:

abstract class Model(val id: Long)

trait Sqlizer[A] {
  def insert(model: A, ret: Boolean = false): String
}

trait Transactor {
  def insert[A <: Model](row: (A, Sqlizer[A])): Transactor

  def commit(): Boolean
}

object Transactor {
  def apply() = new DefaultTransactor
}

class DefaultTransactor extends Transactor {
  private var queries: List[(_ <: Model, Sqlizer[_ <: Model])] = List()

  def insert[A <: Model](row: (A, Sqlizer[A])): Transactor = {
    queries = queries :+ row
    this
  }

  def commit(): Boolean = {
    val body = queries map { kv =>
      kv._2 insert kv._1
    }

    true
  }
}

If I'm not wrong and _ <: Model is like Java's ? extends Model, everything should be ok. But I'm getting error while compilling this file:

Transactor.scala:27: error: type mismatch;
 found   : kv._1.type (with underlying type _$1)
 required: _$2
      kv._2 insert kv._1 

How should I parametrize my queries var to get it right? Or should I change whole typing policy to move Scala way. Thanks.

Upvotes: 0

Views: 241

Answers (1)

Michał Kosmulski
Michał Kosmulski

Reputation: 10020

The problem here is that when you write queries: List[(_ <: Model, Sqlizer[_ <: Model])] it says that the first element in the pair must be of some type that extends Model and the second must be of type Sqlizer parametrized with some type that also extends Model but it doesn't say that the two types must be the same. Since the types could be different, Sqlizer.insert won't work because the types may be incompatible - you can't call Sqlizer[A].insert(param: B) if A and B both extend Model but are not the same type. Below is a modified version where I changed the pair into a case class. Since the case class is parameterized with a single type, its insert method knows that the model and sqlizer it holds are compatible.

abstract class Model(val id: Long)

trait Sqlizer[A] {
  def insert(model: A, ret: Boolean = false): String
}

trait Transactor {
  def insert[A <: Model](row: (A, Sqlizer[A])): Transactor

  def commit(): Boolean
}

object Transactor {
  def apply() = new DefaultTransactor
}

case class Op[T <: Model](model: T, sqlizer: Sqlizer[T]) {
  def insert = sqlizer.insert(model)
}

class DefaultTransactor extends Transactor {
  private var queries: List[Op[_ <: Model]] = List()

  def insert[A <: Model](row: (A, Sqlizer[A])): Transactor = {
    queries = queries :+ Op(row._1, row._2)
    this
  }

  def commit(): Boolean = {
    val body = queries map { kv =>
      kv.insert
    }

    true
  }
}

Upvotes: 2

Related Questions