1

I have the following traits for parsing that give file positions for the beginning and end of the object:

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: " }

In the companion object I create an implicit, so sequences of PosnEnds are themselves implicitly 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)     
  }
}

Is there anyway to use implicits recursively so a Seq[Seq[A]] and a Seq[Seq[Seq[A]]] etc will be implicitly converted to a PosnEnds trait? In practice I probably won't need huge levels of depth, but it would be nice to use an elegant solution that implicitly converted Seq of arbitrary depth.

Currently for depth 2 I'm using:

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)     
}
Rich Oliver
  • 6,001
  • 4
  • 34
  • 57
  • Do you expect operation from `trait PosnEnds` for both cases performed for `A` ? – vvg Nov 24 '15 at 15:15

1 Answers1

7

Yes. You could do it with typeclass mediator.

I allow myself to do some minor changes in your example to make it more reproducible. Inside object PosnEnds I have

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
}

Thing you need first is some simple typeclass like

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

Now you can introduce canonical elements for induction:

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)
  }
}

Finally you can define your implicit conversion

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

From this point

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

compiles and runs succesfully

Major difference with your attempt: we dont wait implicit conversion to stack. Implicit resolution can be recursive, but implicit conversion can not.

So we are using some value-less type, i.e something that could be achieved using only implicit arguments which means could be constructed by the compiler. And only then projecting this logic to the concrete value.

Odomontois
  • 15,918
  • 2
  • 36
  • 71
  • I don't seem to be able to get the implicits to work using your code, even using an import in the use file. – Rich Oliver Nov 25 '15 at 22:15
  • @RichOliver It's sad. Try to put them all in some `object Implicits` and then `import Implicits._`. Note also that if your old `implicit class ImpPosnEndsSeq` is still visible in target source it could conflict with the `def toPosnEnds` – Odomontois Nov 26 '15 at 05:29