I have a trait Location that I don't want to change. O
can be a String
or Seq[String]
trait Location {
type O
def value: O
}
Here is what I want to achieve :
private val stringLog = Log(new Location {
type O = String
def value = "base"
})
private val seqStringLog = Log(new Location {
type O = Seq[String]
def value = Seq("foo", "bar")
})
println(stringLog.getPath("2020"))
println(stringLog.getPath(List("2018", "2019")))
println(seqStringLog.getPath("2020"))
println(seqStringLog.getPath(List("2018", "2019")))
And the expected result :
(in the first case, I have a single location and a single date, so the return type can be String
instead of Seq[String]
)
base/2020
List(base/2018, base/2019)
List(foo/2020, bar/2020)
List(foo/2018, foo/2019, bar/2018, bar/2019)
My current solution uses a type projection. I've seen that it can be an anti pattern and it will be removed in dotty. Is there any cleaner/better solution ?
class Log[L <: Location](location: L)(implicit mapper: Mapper[L#O]) {
def getPath(date: String): L#O =
mapper.applyDate(location.value, date)
def getPath(dates: Seq[String]): Seq[String] =
mapper.applyDates(location.value, dates)
}
trait Mapper[A] {
def applyDate(path: A, date: String): A
def applyDates(path: A, dates: Seq[String]): Seq[String]
}
object Mapper {
def build(path: String, date: String): String = s"$path/$date"
implicit val stringMapper: Mapper[String] = new Mapper[String] {
override def applyDate(path: String, date: String): String = build(path, date)
override def applyDates(path: String, dates: Seq[String]): Seq[String] =
dates.map(build(path, _))
}
implicit val seqStringMapper: Mapper[Seq[String]] = new Mapper[Seq[String]] {
override def applyDate(path: Seq[String], date: String): Seq[String] =
path.map(build(_, date))
override def applyDates(path: Seq[String], dates: Seq[String]): Seq[String] =
path.flatMap(p => dates.map(build(p, _)))
}
}