Reputation: 2616
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
Upvotes: 3
Views: 1006
Reputation: 139058
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 call convert
, then you'll have to make sure that one's in scope every time you call convert convert
. If A
and T
are fixed (and are actually an case class-HList
pair), Shapeless will generate one for you. In your get
method, however, the compiler still knows nothing about A
and T
except that T
is an HList
, so you need to require the instance again in order to call convert
:
def get(index: Int)(implicit gen: Generic.Aux[A, T]): A = convert(rows(index))
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:
implicit def genA: Generic.Aux[A, T]
Then any class implementing LoadsCsv
could have an implicit val genA
parameter (or could supply the instance in some other way).
Upvotes: 6