Reputation: 51
I am trying to understand scala implicits and their usage in type classes. I have a generic trait FromString and a companion object defining implicit instances of FromString for standard types as specified below:
file: /src/main/scala/util/FromString.scala
package util
trait FromString[A] {
def fromString(string: String): A
}
object FromString {
def toFromString[T](p: String => T): FromString[T] = new FromString[T] {
def fromString(x: String): T = p(x);
}
implicit val IntFromString = toFromString[Int](_.toInt);
implicit val ByteFromString = toFromString[Byte](_.toByte);
implicit val LongFromString = toFromString[Long](_.toLong);
implicit val ShortFromString = toFromString[Short](_.toShort);
implicit val FloatFromString = toFromString[Float](_.toFloat);
implicit val DoubleFromString = toFromString[Double](_.toDouble);
implicit val BooleanFromString = toFromString[Boolean](_.toBoolean);
implicit val IntListFromString = toFromString[List[Int]](_.split(',').map(_.toInt).toList);
def convertFromString[A](string: String)(implicit e: FromString[A]): A = e.fromString(string)
}
Now I can use the convertFromString function of the FromString object to convert string to standard types as shown below. This runs correctly.
file: /src/main/scala/top/Main1.scala
package top
import util.FromString._
object Main {
def main(args: Array[String]): Unit = {
val d = convertFromString[Double]("4.5");
val i = convertFromString[Int]("42");
val li = convertFromString[List[Int]]("1,2,3");
println(s"d=$d i=$i li=$li");
}
}
However, when I try to use the same thing from a generic class, as shown below, it results in an error could not find implicit value for parameter e: util.FromString[T]
file: /src/main/scala/util/Knob.scala
package util
import FromString._
class Knob[T](val name: String, default: T){
var value: T = default;
def update(valstr: String) {
value = convertFromString[T](valstr);
}
}
file: /src/main/scala/top/Main2.scala
package top
import util.Knob
import util.FromString._
object Main {
def main(args: Array[String]): Unit = {
val width = new Knob[Int]("Width", 3);
width.update("100");
println(s"width=$width");
}
}
The implicits are defined in the object and I am guessing they are also available in the scope.
Upvotes: 2
Views: 834
Reputation: 44908
The concrete type Int
is known only in the line
val width = new Knob[Int]("Width", 3);
Before this line, the compiler does not know what the type T
will be, and it cannot find and insert the IntFromString
as an implicit argument anywhere. Thus, you have to pass the IntFromString
from the invocation site in the main
to the use site in Knob.update
. For this, just add the FromString
typeclass to T
:
class Knob[T: FromString](val name: String, default: T) {
...
}
General rule when programming with generic types and typeclasses: the concrete typeclass instances must be inserted "at the end of the world", in the line where the generic type parameters (like T
) are fixed and become concrete types (like Int
). You have to pass those typeclass instances all the way from the site where the concrete types can be determined to the generic code that uses the typeclass instances.
trait FromString[A] {
def fromString(string: String): A
}
object FromString {
def toFromString[T](p: String => T): FromString[T] = new FromString[T] {
def fromString(x: String): T = p(x);
}
implicit val IntFromString = toFromString[Int](_.toInt);
implicit val ByteFromString = toFromString[Byte](_.toByte);
implicit val LongFromString = toFromString[Long](_.toLong);
implicit val ShortFromString = toFromString[Short](_.toShort);
implicit val FloatFromString = toFromString[Float](_.toFloat);
implicit val DoubleFromString = toFromString[Double](_.toDouble);
implicit val BooleanFromString = toFromString[Boolean](_.toBoolean);
implicit val IntListFromString = toFromString[List[Int]](_.split(',').map(_.toInt).toList);
def convertFromString[A](string: String)(implicit e: FromString[A]): A = e.fromString(string)
}
import FromString._
class Knob[T: FromString](val name: String, default: T) {
var value: T = default
def update(valstr: String) {
value = convertFromString[T](valstr)
}
}
object Main {
def main(args: Array[String]): Unit = {
val d = convertFromString[Double]("4.5");
val i = convertFromString[Int]("42");
val li = convertFromString[List[Int]]("1,2,3");
println(s"d=$d i=$i li=$li");
val width = new Knob[Int]("Width", 3)
width.update("100")
println(s"width=$width")
}
}
Upvotes: 2