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?

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top