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
компилируется и запускается успешно
Основная разница с вашей попыткой: мы не ждем неявного преобразования в стек. Неявное разрешение может быть рекурсивным, но неявное преобразование не может.
Таким образом, мы используем некоторый бесполезный тип, то есть то, что может быть достигнуто с использованием только неявных аргументов, что означает, что компилятор может создать их. И только потом проецируем эту логику на конкретную ценность.