Search code examples
scalatypeclasspath-dependent-type

How to define a helper types to be path-independent in a Scala type class?


Let us have a type class which depending on the type defines some more types to work with:

trait Container[T] {
  type Elem

  def get(c: T, i: Int): Elem
  def set(c: String, i: Int, v: Elem): T
}

implicit object StringContainer extends Container[String] {
  type Elem = Char

  def get(c: String, i: Int) = c(i)
  def set(c: String, i: Int, v: Char) = c.patch(i, Seq(v), 1)
}

val ops = implicitly[Container[String]]


ops.set("ABC", 1, ops.get("ABC", 1)) // works

ops.set("ABC", 1, 'X') // type mismatch; found   : Char('X') required: ops.Elem

Because types are path dependent the compiler complains when trying to use this, the error is:

type mismatch;

found : Char('X')

required: ops.Elem

You and I know ops.Elem is Char. My current workaround is to use Elem as a type parameter instead:

trait Container[T, Elem] {
  def get(c: T, i: Int): Elem
  def set(c: String, i: Int, v: Elem): T
}

implicit object StringContainer extends Container[String, Char] {
  def get(c: String, i: Int) = c(i)
  def set(c: String, i: Int, v: Char) = c.patch(i, Seq(v), 1)
}

The drawback is to recall the type class when needed one needs to provide all type arguments:

val ops = implicitly[Container[String, Char]]

Is there some way to define types in type-class so that they can be used as path-independent?


Solution

  • You are requesting just

    Container[String]
    

    instead of

    Container[String] { type Elem = Char }
    

    Try with type refinement

    object Container {
      implicit val strContainer: Container[String] { type Elem = Char } = new Container[String] {
        type Elem = Char
    
        def get(c: String, i: Int) = c(i)
        def set(c: String, i: Int, v: Char) = c.patch(i, Seq(v), 1)
      }
    }
    
    val ops = implicitly[Container[String] { type Elem = Char }]
    ops.set("ABC", 1, 'X') // ok
    

    which with Aux pattern becomes something like

    object Container {
      type Aux[T,Elem0] = Container[T] { type Elem = Elem0 }
    
      implicit val strContainer: Aux[String, Char] = new Container[String] {
        type Elem = Char
    
        def get(c: String, i: Int) = c(i)
        def set(c: String, i: Int, v: Char) = c.patch(i, Seq(v), 1)
      }
    }
    
    
    val ops = implicitly[Container.Aux[String,Char]]
    ops.set("ABC", 1, 'X') // ok