如何避免在Scala中使用类型投影

如何避免在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

我有一个我不想改变的特质位置。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", "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)
}