2
votes

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.

1

1 Answers

2
votes

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")
    }
}