I have a couple of solutions to a "double definition" problem, but I can't figure what they're really doing to work around the type erasure issue.
I'll give some general context as well, since I'm probably approaching the problem wrong in the first place, but ultimately help understanding DummyImplicits & by-name params in this context is enough.
Context
I'm replacing parsers for deeply nested JSON where pretty much every value is optional, and nearly all data (including Int, Double, etc.) is stored as Strings. The classes that catch the parsed values take this general form (for now).
case class Field1(subfield1: Option[String], subfield2: Option[String]) {
def sf1Converted: Option[Int] =
Try { subfield1.get.toInt }.filter(i => lb <= i && i <= ub).toOption
def sf2Converted: Option[Double] =
Try { subfield2.get.toDouble }.filter(d => lb <= d && d <= ub).toOption
}
This functions, but... it's incredibly slow and requires unreal amounts of memory. My guess is converting the Strings to numeric data out of the gate will reduce the memory considerably, and probably speed up the usage of the data at the expense of load times.
So I figured I'd do something like...
/** type signature w/ numeric args instead of Strings */
case class Field1(subfield1: Option[Int], subfield2: Option[Double])
object Field1 {
/** the apply method I need for this class */
def apply(subfield1: Option[String], subfield2: Option[String]): Field1 =
new Field1(
Try { subfield1.get.toInt }.filter(i => lb <= i && i <= ub).toOption,
Try { subfield2.get.toDouble }.filter(d => lb <= d && d <= ub).toOption)
/** one more to generalize the question a bit more */
def apply(subfield1: Option[Int], subfield2: Option[Int]): Field1 =
new Field1(subfield1, Try { subfield2.get.toDouble }.filter(d => lb <= d && d <= ub).toOption)
}
This obviously throws a couple error: double definition ... have the same type after erasure errors though. So I've found two solutions... but I can't figure what either one is actually doing and what problems I might get into down the road with one or the other.
option 1 make one (different) arg in each apply method a by-name param
object Field1 {
def apply(subfield1: => Option[String], subfield2: Option[String]): Field1 = ???
def apply(subfield1: Option[Int], subfield2: => Option[Int]): Field1 = ???
}
option 2 dummy implicits
object Field1 {
def apply(subfield1: Option[String], subfield2: Option[String])(implicit i: DummyImplicit): Field1 = ???
def apply(subfield1: Option[Int], subfield2: Option[Int])(implicit i: DummyImplicit, i2: DummyImplicit): Field1 = ???
}
question
Both of these compile, but I don't understand why either one works around the type erasure issue.
I thought by-name params were only evaluated when used, and evaluated every time they're used. I can't figure out why this fixes type erasure issues.
There's a pretty good demonstration of dummy implicits here, but when it gets to the core of the issue, it just says "each call was given the implicit parameter needed to properly disambiguate the call". How does a dummy value do this?
The docs just say "A type for which there is always an implicit value."
May be a bad question, since I know how to make it work, but I'm totally lost re why either option works at all. Any help appreciated.
CodePudding user response:
The relevant section of compiled code for option-1 looks like
def apply(subfield1: Function0, subfield2: Option): Field1 = scala.Predef.???();
def apply(subfield1: Option, subfield2: Function0): Field1 = scala.Predef.???();
As you can see by-name parameters are converted to Function0, so reusing the name is possible.
For option-2 the implicit curried parameters are added as extra parameters to functions, thus making it possible to use the same name.
def apply(subfield1: Option, subfield2: Option, i: DummyImplicit): Field1 = scala.Predef.???();
def apply(subfield1: Option, subfield2: Option, i: DummyImplicit, i2: DummyImplicit): Field1 = scala.Predef.???();
You can also see that type erasure does happen.
EDIT - Using TypeTag to overcome type erasure
def apply[T: TypeTag](subfield1: => Option[T], subfield2: Option[T]): Field1 =
typeOf[T] match {
case tpe if tpe =:= typeOf[String] => new Field1(subfield1.asInstanceOf[Option[String]], subfield2.asInstanceOf[Option[String]])
case tpe if tpe =:= typeOf[Int] => ???
case _ => new Field1(subfield1.map(_.toString), subfield2.map(_.toString))
}
