I just recently started learning scala and today I decided I wanted to write a CSV parser that would load nicely into case classes but store the data in rows (lists) of Shapeless's HList object so that I could get some exposure to type-level programming.
Here's what I have so far:
// LoadsCsv.scala
import shapeless._
import scala.collection.mutable
trait LoadsCsv[A, T <: HList] {
val rows: mutable.MutableList[T] = new mutable.MutableList[T]
def convert(t: T)(implicit gen: Generic.Aux[A, T]): A = gen.from(t)
def get(index: Int): A = {
convert(rows(index))
}
def load(file: String): Unit = {
val lines = io.Source.fromFile(file).getLines()
lines.foreach(line => rows += parse(line.split(",")))
}
def parse(line: Array[String]): T
}
And the object that's loading the data set:
// TennisData.scala
import shapeless._
case class TennisData(weather:String, low:Int, high:Int, windy:Boolean, play:Boolean)
object TennisData extends LoadsCsv[TennisData, String :: Int :: Int :: Boolean :: Boolean :: HNil] {
load("tennis.csv")
override def parse(line: Array[String]) = {
line(0) :: line(1).toInt :: line(2).toInt :: line(3).toBoolean :: line(4).toBoolean :: HNil
}
}
Things seem to be working alright until I added the get() with the conversion from the HList to the case class where I now get a compilation error. Why isn't the implicit getting loaded and what can I do to fix it or otherwise convert from the HList to the case class?
Error:(14, 17) could not find implicit value for parameter gen: shapeless.Generic.Aux[A,T]
return convert(rows(index))
^
I've been reading the shapeless documentation and it mentions that this area had been in flux between version 1 and 2, but I believe things should be working on my version of shapeless and scala so I suspect I've just done something incorrectly.
For reference, I'm running scala 2.11.6 and shapeless 2.2.2
You're very close. The problem is that Scala isn't going to propagate implicit requirements up the call chain automatically for you. If you need a
Generic[A, T]instance to callconvert, then you'll have to make sure that one's in scope every time you call convertconvert. IfAandTare fixed (and are actually an case class-HListpair), Shapeless will generate one for you. In yourgetmethod, however, the compiler still knows nothing aboutAandTexcept thatTis anHList, so you need to require the instance again in order to callconvert:Everything should work just fine after this change.
Note that you could also require the instance at the trait level by adding an (abstract) method like the following:
Then any class implementing
LoadsCsvcould have an implicitval genAparameter (or could supply the instance in some other way).