How to avoid losing type information
Question
Suppose I have something like this:
trait Cursor {
}
trait Column[T] {
def read(cusor: Cursor): T
}
trait ColumnReader {
def readColumns(columns: Product[Column[_]], cursor: Cursor): Iterable[Any] = {
for (column <- columns) yield column.read(cursor)
}
}
The problem of the readColumns()
API is that I lose the type information, i.e., if I have this:
object columnString extends Column[String] {
def read(cursor: Cursor): String = ...
}
object columnInt extends Column[Int] {
def read(cursor: Cursor): Int = ...
}
An expression like new ColumnReader().readColumns((columnString, columnInt))
returns Iterable[Any]
. I would like to return something typed like Tuple2[String, Int]
, but don't know how. I lose type information useful to the compiler.
Maybe a library like Shapeless could be useful.
I'm sure Scala has some tool for dealing on problems like this.
Any ideas?
Solution
An example using a shapeless HList
class Cursor
trait Column[T] {
def read(cusor: Cursor): T
}
class CursorContainer(cursor: Cursor) {
object mapper extends Poly1 {
implicit def any[T] = at[Column[T]](_.read(cursor))
}
}
class ColumnReader {
def readColumns[T <: HList](columns: T, cursor: CursorContainer)(
implicit m:Mapper[cursor.mapper.type, T]) = columns.map(cursor.mapper)
}
val columnString = new Column[String] {
def read(cursor: Cursor): String = ???
}
val columnInt = new Column[Int] {
def read(cursor: Cursor): Int = ???
}
val reader = new ColumnReader
val cursor = new CursorContainer(new Cursor)
val result: String :: Int :: HNil =
reader.readColumns(columnString :: columnInt :: HNil, cursor)
OTHER TIPS
Why not use a container that can take type parameters, e.g. Seq or List?
trait Cursor {
}
trait Column[T] {
def read(cusor: Cursor): T
}
trait ColumnReader[T] {
def readColumns(columns: Seq[Column[T]], cursor: Cursor): Iterable[T] = {
for (column <- columns) yield column.read(cursor)
}
}
You need an HList from Shapeless
You could also use an Applicative if your number of columns is limited.
trait Column[T] {
def read(c: Cursor) : Id[T]
}
object columnString extends Column[String]
{
override def read(c: Cursor): Id[String] = "hello"
}
object columnInt extends Column[Int] {
override def read(c: Cursor): Id[Int] = 3
}
type ColumnReader[T] = Reader[Cursor, T]
val readSpreadSheet1 : ColumnReader[(Int, String)] = Reader {
c =>
(columnInt.read(c) |@| columnString.read(c)) { (_,_)}
}
readSpreadSheet1(c)
would result in:
res1: scalaz.Id.Id[(Int, String)] = (3,hello)
I have also thrown in a little Reader definition, so that you do not need to care about passing around the instance of cursor when you read a row. On the downside, you need to know the types of your columns beforehand, but I think this would also be true when you use an HList.