如何避免在Scala中使用类型投影
我有一个我不想改变的特质位置。O可以是字符串或Seq[String] 以下是我想要实现的目标:如何避免在Scala中使用类型投影,scala,typeclass,type-projection,Scala,Typeclass,Type Projection,我有一个我不想改变的特质位置。O可以是字符串或Seq[String] 以下是我想要实现的目标: 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", &q
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]
我当前的解决方案使用类型投影。我已经看到,它可以是一个反模式,它将在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, _)))
}
}
这对我很有用:
final class Log[L <: Location](val location: L) {
def getPath(date: String)
(implicit mapper: Mapper[location.O]): location.O =
mapper.applyDate(location.value, date)
def getPath(dates: List[String])
(implicit mapper: Mapper[location.O]): List[String] =
mapper.applyDates(location.value, dates)
}
你想用什么就用什么
顺便说一句,我建议你远离Seq,使用一个像List这样的具体集合。有关更多信息,请参阅
编辑
确保只有在有映射器并保持位置封装的情况下才能创建日志实例
sealed trait Log[L <: Location] {
protected type LL <: L
protected val l: LL
def getPath(date: String): l.O
def getPath(dates: List[String]): List[String]
}
object Log {
def apply[L <: Location](location: L)
(implicit mapper: Mapper[location.O]): Log[L] = new Log[L] {
override protected final type LL = location.type
override protected final val l: LL = location
override def getPath(date: String): l.O =
mapper.applyDate(l.value, date)
override def getPath(dates: List[String]): List[String] =
mapper.applyDates(l.value, dates)
}
}
我看不出有任何理由不在这里简单地使用类型参数
trait Location[O] {
def value: O
}
您所要做的就是让Log接受O而不是位置。它也比具有类型成员更简洁,因为您可以简单地执行新位置[String]{},而不是新位置{typeo=String}
试一试精致型
case class Log[_O](location: Location { type O = _O })(implicit mapper: Mapper[_O]) {
def getPath(date: String): _O =
mapper.applyDate(location.value, date)
def getPath(dates: Seq[String]): Seq[String] =
mapper.applyDates(location.value, dates)
}
您可以引入Aux类型Aux[\u O]=Location{type O=\u O}和write Location.Aux[\u O]
在Dotty中,类型类和匹配类型是类型投影的两种替代
为什么不把O作为类型参数?主要是为了避免在Log:Log[O,L中有两个类型参数,您可以执行Log[O]location:location[O]我有一个类型参数的答案,我删除了它,但改变了位置。如果你想感谢你指出你的建议中Seq.2的缺点,我可以取消删除它:可以实例化一个没有映射器实例的日志。位置现在是公共的,这打破了一点封装。@Yannomisan可以看到编辑过的答案,顺便说一句,这是通用的这是一个相当复杂的问题,因为正如我之前所说的,不清楚你真正的最终目标是什么。例如,你真的需要依赖类型吗?你真的需要精确的L类型吗?一个简单的解决方法就是使用Dmytro建议的Aux模式。
trait Location[O] {
def value: O
}
class Log[O](location: Location[O])(implicit mapper: Mapper[O]) {
def getPath(date: String): O =
mapper.applyDate(location.value, date)
}
private val stringLog = new Log(new Location[String] {
def value = "base"
})
private val seqStringLog = new Log(new Location[Seq[String]] {
def value = Seq("foo", "bar")
})
case class Log[_O](location: Location { type O = _O })(implicit mapper: Mapper[_O]) {
def getPath(date: String): _O =
mapper.applyDate(location.value, date)
def getPath(dates: Seq[String]): Seq[String] =
mapper.applyDates(location.value, dates)
}