I'm running into an issue where I am working with several Traits that use dependent typing, but when I try to combine the Traits in my business logic, I get a compilation error.
import java.util.UUID
object TestDependentTypes extends App{
val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))
RealConfLoader(7).extractData(myConf.settings)
}
trait Data
case class RealData(anInt: Int, aDouble: Double) extends Data
trait MySettings
case class RealSettings(data: RealData) extends MySettings
trait Conf {
type T <: MySettings
def id: UUID
def settings: T
}
case class RealConf(id: UUID, settings: RealSettings) extends Conf {
type T = RealSettings
}
trait ConfLoader{
type T <: MySettings
type U <: Data
def extractData(settings: T): U
}
case class RealConfLoader(someInfo: Int) extends ConfLoader {
type T = RealSettings
type U = RealData
override def extractData(settings: RealSettings): RealData = settings.data
}
The code in processor
will not compile because extractData
expects input of type ConfLoader.T
, but conf.settings
is of type Conf.T
. Those are different types.
However, I have specified that both must be subclasses of MySettings
, so it should be the case I can use one where the other is desired. I understand Scala does not compile the code, but is there some workaround so that I can pass conf.settings
to confLoader.extractData
?
===
I want to report that for the code I wrote above, there is a way to write it that would decrease my usage of dependent types. I noticed today while experimenting with Traits that Scala supports subclassing on defs and vals on classes that implement the Trait. So I only need to create a dependent type for the argument for extractData
, and not the output.
import java.util.UUID
object TestDependentTypes extends App{
val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))
RealConfLoader(7).extractData(myConf.settings)
def processor(confLoader: ConfLoader, conf: Conf) = confLoader.extractData(conf.settings.asInstanceOf[confLoader.T])
}
trait Data
case class RealData(anInt: Int, aDouble: Double) extends Data
trait MySettings
case class RealSettings(data: RealData) extends MySettings
trait Conf {
def id: UUID
def settings: MySettings
}
case class RealConf(id: UUID, settings: RealSettings) extends Conf
trait ConfLoader{
type T <: MySettings
def extractData(settings: T): Data
}
case class RealConfLoader(someInfo: Int) extends ConfLoader {
type T = RealSettings
override def extractData(settings: RealSettings): RealData = settings.data
}
The above code does the same thing and reduces dependence on dependent types. I have only removed processor
from the code. For the implementation of processor, refer to any of the solutions below.
The code in
processor
will not compile becauseextractData
expects input of typeConfLoader.T
, butconf.settings
is of typeConf.T
. Those are different types.
In the method processor
you should specify that these types are the same.
Use type refinements (1, 2) for that: either
def processor[_T](confLoader: ConfLoader { type T = _T }, conf: Conf { type T = _T }) =
confLoader.extractData(conf.settings)
or
def processor(confLoader: ConfLoader)(conf: Conf { type T = confLoader.T }) =
confLoader.extractData(conf.settings)
or
def processor(conf: Conf)(confLoader: ConfLoader { type T = conf.T }) =
confLoader.extractData(conf.settings)