Scala: рекурсивный неявный тип

У меня есть следующие черты для разбора, которые дают файловые позиции для начала и конца объекта:

case class FilePosn(lineNum :Int, tabs: Int, spaces: Int, fileName: String)
{/*code omitted*/}

trait PosnEnds
{
  def startPosn: FilePosn
  def endPosn: FilePosn
  def multiLine: Boolean = startPosn.lineNum != endPosn.lineNum
  def OneLine: Boolean = startPosn.lineNum == endPosn.lineNum
  def indent: Int = startPosn.tabs
  def startLine: Int = startPosn.lineNum
  def endLine: Int = endPosn.lineNum
}

object FilePosnVoid extends FilePosn(0, 0, 0, "There is no File position")
{ override def posnString(indentSize: Int): String = "No File Posn: " }

В объекте-компаньоне я создаю неявный, поэтому последовательности PosnEnds сами по себе являются неявными PosnEnds:

object PosnEnds
{
  implicit class ImpPosnEndsSeq[A <: PosnEnds](thisSeq: Seq[A]) extends PosnEnds
  {
    override def startPosn: FilePosn = thisSeq.fHead(FilePosnVoid, (h, t) => h.startPosn)
    override def endPosn: FilePosn = thisSeq.fLast(FilePosnVoid, _.endPosn)     
  }
}

Есть ли возможность использовать имплициты рекурсивно, поэтому Seq[Seq[A]] и Seq[Seq[Seq[A]]] и т. Д. Будут неявно преобразованы в черту PosnEnds? На практике мне, вероятно, не понадобятся огромные уровни глубины, но было бы неплохо использовать элегантное решение, которое неявно преобразует Seq произвольной глубины.

В настоящее время для глубины 2 я использую:

implicit class ImpPosnEndsSeqSeq[A <: PosnEnds](thisSeq: Seq[Seq[A]]) extends PosnEnds
{
  override def startPosn: FilePosn = thisSeq.fHead(FilePosnVoid, (h, t) => h.startPosn)
  override def endPosn: FilePosn = thisSeq.fLast(FilePosnVoid, _.endPosn)     
}

1 ответ

Да. Вы можете сделать это с помощью медиатора класса типов.

Я позволю себе внести некоторые незначительные изменения в ваш пример, чтобы сделать его более воспроизводимым. внутри object PosnEnds я имею

val void = new FilePosn(0, 0, 0, "There is no File position") {
  override def posnString(indentSize: Int): String = "No File Posn: "
}

def empty = new PosnEnds {
  def startPosn: FilePosn = void
  def endPosn: FilePosn = void
}

Сначала вам нужен простой класс типов

trait MakePosnEnds[X] extends (X => PosnEnds)

Теперь вы можете ввести канонические элементы для индукции:

implicit object idMakePosnEnds extends MakePosnEnds[PosnEnds] {
  def apply(x: PosnEnds) = x
}

implicit def seqMakePosnEnds[X](implicit recur: MakePosnEnds[X]) = new MakePosnEnds[Seq[X]] {
  def apply(x: Seq[X]): PosnEnds = new PosnEnds {
    val thisSeq = x.map(recur)
    override def startPosn: FilePosn = thisSeq.headOption.fold(void)(_.startPosn)
    override def endPosn: FilePosn = thisSeq.lastOption.fold(void)(_.endPosn)
  }
}

Наконец, вы можете определить ваше неявное преобразование

implicit def toPosnEnds[X](x: X)(implicit make: MakePosnEnds[X]): PosnEnds = make(x)

С этого момента

Seq(Seq(Seq(empty))).startLine

компилируется и запускается успешно

Основная разница с вашей попыткой: мы не ждем неявного преобразования в стек. Неявное разрешение может быть рекурсивным, но неявное преобразование не может.

Таким образом, мы используем некоторый бесполезный тип, то есть то, что может быть достигнуто с использованием только неявных аргументов, что означает, что компилятор может создать их. И только потом проецируем эту логику на конкретную ценность.

Другие вопросы по тегам