case class Column[A](name: String) object Column { def string(name: String): Column[String] = Column[String](name) def int(name: String): Column[Int] = Column[Int](name) } // Type arguments are unchecked because they are eliminated by erasure at runtime def select[A](c: Column[A]): A = c match { case _: Column[String] => "String" case _: Column[Int] => 42 } // select(Column.int("col1")) == 42 // this will throw a ClassCastException at runtime /// How to fix this? case class ColumnFix[A: ColumnType](name: String) { // *** def columnType: ColumnType[A] = implicitly[ColumnType[A]] } // 1. Create a new trait to represent a column types sealed trait ColumnType[A] // 2. Create a companion object for each column type as implicit values object ColumnType { implicit case object StringColumnType extends ColumnType[String] implicit case object IntColumnType extends ColumnType[Int] } object ColumnFix { def string(name: String): ColumnFix[String] = ColumnFix[String](name) def int(name: String): ColumnFix[Int] = ColumnFix[Int](name) } // 3. Create a function in the `ColumnFix` class that returns the type of the column and add a context bounded to the column type *** // 4. Create a function to select a column with context bound to the column type. // This will tell the compiler that we can call the function as long as there is an implicit instance for the column type defined for A. def selectFix[A: ColumnType](c: ColumnFix[A]): A = c.columnType match { case ColumnType.IntColumnType => 42 case ColumnType.StringColumnType => "String" } selectFix(ColumnFix.int("col1")) == 42 // res0: Boolean = true