Hi,
I am looking for a way to pack and unpack values of different types to and from a
Val
class in a Map[String,Val]
. Val
here is basically a value type. I first tried this with
implicits and type parameters and could not get it to work. I then got
it working with type members.
So this is what I have:
First I have the Val
and the implicits that pack the value.
import scala.language.implicitConversions
sealed trait Val {
type V
val v: V
}
case class ErrVal(override val v:String) extends Val {
type V = String
}
implicit def strVal(a:String): Val = new Val {
override type V = String
override val v: V = a
}
implicit def intVal(a:Int): Val = new Val {
override type V = Int
override val v: V = a
}
I also have the implicits that unpack the value:
def errMsg(expected:String, a:Val) = Left(s"Expected a $expected but got ${a.v.getClass}")
implicit def valStr(a:Val): Either[String,String] = a.v match {
case e:String => Right(e)
case _ => errMsg("String", a)
}
implicit def valInt(a:Val): Either[String,Int] = a.v match {
case e:Int => Right(e)
case _ => errMsg("Int", a)
}
The following class Row
is used to process the value. It simply allows one to apply
a function and let the implicits unpack and pack the values as required
(apply simply applies the function f
to a number of elements):
case class Row(r:Map[String,Val]) {
def apply[A,B](f: A => B, cols:String*)(implicit a: Val => Either[String,A], b: B => Val): Row = {
val cls = cols.toSet
val t = r.filterKeys( cls.contains ).map { case (k,v) =>
// Get the row element
val va = a(v)
val e = va.fold( { msg =>
// If it is not the correct type, error
ErrVal(msg)
}, { oa =>
// If it is the correct type, use it
val vb = f(oa)
val avb = b(vb)
avb
})
(k, e)
}
// Replace what we changed
Row(r ++ t)
}
}
And I can use it so:
def main(args: Array[String]): Unit = {
val data0 = Frame(
"x" -> Seq("1","1","1","0").toIterator,
"y" -> Seq("2","2","2","0").toIterator)
//println(data0)
val data1 = Frame(
"x" -> Seq(1,1,1,0),
"y" -> Seq(2,2,2,0))
//println(data1)
val r1: Row = data0.iterable.head
println(r1)
val r2 = r1( (x:String) => x.toInt, "x", "y" )
println(r2)
val r3: Row = data1.iterable.head
println(r3)
val r4 = r3( (x:String) => x.toInt, "x", "y" )
println(r4)
}
which produces something like:
Row(Map(x -> AutoTask$$anon$3@617c74e5, y -> AutoTask$$anon$3@34b7bfc0))
Row(Map(x -> AutoTask$$anon$4@7c0e2abd, y -> AutoTask$$anon$4@48eff760))
Row(Map(x -> AutoTask$$anon$4@402f32ff, y -> AutoTask$$anon$4@573f2bb1))
Row(Map(x -> ErrVal(Expected a String but got class java.lang.Integer), y -> ErrVal(Expected a String but got class java.lang.Integer)))
Ok, so this is all well and good but I or anyone else that wants to use Row
with new
types must always define the implicit conversions for those types . But this is simple
repetitive boilerplate. The question is how can I automate this? What is the simplest
most maintenance friendly way of doing this?
TIA,
Code follows in case anyone wants to experiment with it.
import scala.collection.AbstractIterator
object AutoTask {
import scala.language.implicitConversions
sealed trait Val {
type V
val v: V
}
case class ErrVal(override val v:String) extends Val {
type V = String
}
implicit def strVal(a:String): Val = new Val {
override type V = String
override val v: V = a
}
implicit def intVal(a:Int): Val = new Val {
override type V = Int
override val v: V = a
}
// https://medium.com/@sinisalouc/overcoming-type-erasure-in-scala-8f2422070d20
// non-variable type argument String in type pattern AutoTask.Val[String] is unchecked since it is eliminated by erasure
/*
implicit def valStr[T](a:Val[T])(implicit tag: TypeTag[T]):String =
tag.tpe match {
case TypeRef(utype, usymbol, args) =>
List(utype, usymbol, args).mkString("\n")
}
*/
def errMsg(expected:String, a:Val) = Left(s"Expected a $expected but got ${a.v.getClass}")
implicit def valStr(a:Val): Either[String,String] = a.v match {
case e:String => Right(e)
case _ => errMsg("String", a)
}
implicit def valInt(a:Val): Either[String,Int] = a.v match {
case e:Int => Right(e)
case _ => errMsg("Int", a)
}
case class Row(r:Map[String,Val]) {
def apply[A,B](f: A => B, cols:String*)(implicit a: Val => Either[String,A], b: B => Val): Row = {
val cls = cols.toSet
val t = r.filterKeys( cls.contains ).map { case (k,v) =>
// Get the row element
val va = a(v)
val e = va.fold( { msg =>
// If it is not the correct type, error
ErrVal(msg)
}, { oa =>
// If it is the correct type, use it
val vb = f(oa)
val avb = b(vb)
avb
})
(k, e)
}
// Replace what we changed
Row(r ++ t)
}
}
case class Frame(src: Iterable[Row]) {
def iterable: Iterable[Row] = {
src.toIterator.map { row => row /* TODO */ }
}.toIterable
}
object Frame {
def apply[T](data: (String, Iterator[T])*)(implicit f: T => Val): Frame = {
// Create iterator on data
val src = new Iterable[Row] {
override def iterator: Iterator[Row] = new AbstractIterator[Row] {
override def hasNext: Boolean = data.forall( p => p._2.hasNext)
override def next(): Row = {
val cols = data.map { case (k,v) =>
val vv = f( v.next() )
(k,vv)
}
Row(cols.toMap)
}
}
}
new Frame(src)
}
// https://stackoverflow.com/questions/3307427/scala-double-definition-2-methods-have-the-same-type-erasure
def apply[T](data: (String, Seq[T])*)(implicit f: T => Val, d: DummyImplicit): Frame = {
val ndata = data.map{ case (k,v) => (k,v.toIterator) }
apply(ndata:_*)(f)
}
}
def main(args: Array[String]): Unit = {
val data0 = Frame(
"x" -> Seq("1","1","1","0").toIterator,
"y" -> Seq("2","2","2","0").toIterator)
//println(data0)
val data1 = Frame(
"x" -> Seq(1,1,1,0),
"y" -> Seq(2,2,2,0))
//println(data1)
val r1: Row = data0.iterable.head
println(r1)
val r2 = r1( (x:String) => x.toInt, "x", "y" )
println(r2)
val r3: Row = data1.iterable.head
println(r3)
val r4 = r3( (x:String) => x.toInt, "x", "y" )
println(r4)
}
}