【发布时间】:2020-10-28 03:04:46
【问题描述】:
我有一个我不想改变的特征位置。 O 可以是 String 或 Seq[String]
trait Location {
type O
def value: O
}
这是我想要实现的目标:
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")))
和预期的结果:
(在第一种情况下,我只有一个位置和一个日期,所以返回类型可以是String而不是Seq[String])
base/2020
List(base/2018, base/2019)
List(foo/2020, bar/2020)
List(foo/2018, foo/2019, bar/2018, bar/2019)
我当前的解决方案使用类型投影。我已经看到它可以是一种反模式,它会在 dotty 中被删除。有没有更清洁/更好的解决方案?
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, _)))
}
}
【问题讨论】:
-
为什么不用
O作为类型参数? -
主要是为了避免Log中有2个类型参数:
Log[O, L <: Location[O]]。我已经尝试过这两个 impl。在这种情况下,我发现 type member 更优雅。 -
可以做Log[O](location: Location[O])
-
我有一个类型参数的答案,我删除了,但更改了
Location。如果你愿意,我可以取消删除它
标签: scala typeclass type-projection