Reputation: 591
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
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