Search code examples
scalapath-dependent-type

Path-dependent typing over match/case


sealed trait Desc {
  type T
}

trait Dataset[A] {
  def toDS[A] = new Dataset[A] {}
}
trait DataFrame {}


sealed trait DFDesc extends Desc {
  type T = Dummy
}

sealed trait DSDesc[A] extends Desc {
  type T = A
}

trait JobConstruction {
  def apply(desc: Desc): Job[desc.T]
}

sealed trait Job[DescType] {
  def description: Desc { type T = DescType }
}

abstract class DSJob[V] extends Job[V] {
  def result(con: JobConstruction): Dataset[V]
}

abstract class DFJob extends Job[Dummy] {
  def result(con: JobConstruction): DataFrame
}

trait Dummy {}

case class SampleDFDesc() extends DFDesc
case class SampleDFJob(description: SampleDFDesc) extends DFJob {
  override def result(con: JobConstruction) = new DataFrame {}
}
case class SampleDSDesc() extends DSDesc[Int]
case class SampleDSJob(description: SampleDSDesc) extends DSJob[Int] {
  override def result(con: JobConstruction) = new Dataset[Int] {}
}

object Main {
  val sampleConst = new JobConstruction {
    override def apply(desc: Desc): Job[desc.T] = desc match {
      case desc2: SampleDFDesc => SampleDFJob(desc2)
      case desc2: SampleDSDesc => SampleDSJob(desc2)
    }
  }
}

Fails to compile with

/tmp/sample.scala:73: error: type mismatch;
found   : this.SampleDFJob
required: this.Job[desc.T]
      case desc2: SampleDFDesc => SampleDFJob(desc2)
                                            ^
/tmp/sample.scala:74: error: type mismatch;
found   : this.SampleDSJob
required: this.Job[desc.T]
      case desc2: SampleDSDesc => SampleDSJob(desc2)

EDIT:

I would like to get this to work in some kind:

case class SampleDepDesc(df: SampleDFDesc) extends DSDesc[Int]
case class SampleDepJob(description: SampleDepDesc) extends DSJob[Int] {
  override def result(con: JobConstruction): Dataset[Int] = con(description.df).result(con).toDS[Int]
}

Solution

  • It is not a real solution, but you could replace type inside trait, with type parameter, this code compiles:

    sealed trait Desc[T]
    
    trait Dataset[A]
    trait DataFrame
    
    sealed trait DFDesc extends Desc[Dummy]
    
    sealed trait DSDesc[A] extends Desc[A]
    
    trait JobConstruction {
      def apply[A](desc: Desc[A]): Job[A]
    }
    
    sealed trait Job[A] {
      def description: Desc[A]
    }
    
    abstract class DSJob[V] extends Job[V] {
      def result: Dataset[V]
    }
    
    abstract class DFJob extends Job[Dummy] {
      def result: DataFrame
    }
    
    trait Dummy
    
    case class SampleDFDesc() extends DFDesc
    case class SampleDFJob(description: SampleDFDesc) extends DFJob {
      def result = new DataFrame {}
    }
    case class SampleDSDesc() extends DSDesc[Int]
    case class SampleDSJob(description: SampleDSDesc) extends DSJob[Int] {
      def result = new Dataset[Int] {}
    }
    
    val sampleConst = new JobConstruction {
      override def apply[A](desc: Desc[A]): Job[A] = desc match {
        case desc2: SampleDFDesc => SampleDFJob(desc2)
        case desc2: SampleDSDesc => SampleDSJob(desc2)
      }
    }
    

    As for how to make path dependent types work, I am curious myself.