2

I've defined the following class, PositionSpace, which has nested classes Position and SubPositionSpace. SubPositionSpace is itself both a Position and a PositionSpace (so it is a position that also has its own positions defined). I've defined a wrapper class, SubPosition, so that any object of type x.SubPositionSpace#Position (for any object x that is a PositionSpace) can be used as an object of type x.Position. However, I would like to make this conversion implicit, and that's where I'm stuck. Logically, it should be possible. But, I can't figure out where or how to define the conversion so that the compiler finds it, and it's making my head hurt.

Below is my code. Note that I've added test cases to the bottom.

class PositionSpace { positionSpace =>

  //snip

  trait Position extends Ordered[Position] {
    //snip
  }

  class SubPositionSpace() extends PositionSpace with Position { subPositionSpace =>

    //snip

    //wrapper class to treat a x.SubPositionSpace#Position as a x.Position for some x : PositionSpace
    //want to make this conversion implicit!!!
    class SubPosition(private val subPositionIndex : subPositionSpace.Position) extends positionSpace.Position {
      //snip
    }
  }

  def append() = new Position{
    //snip
  }

  def appendSubSpace() = new SubPositionSpace()
}

object TestCases {
  val positionSpace = new PositionSpace()
  val subSpace = positionSpace.appendSubSpace()
  val subSpacePosition = subSpace.append()
  //Does not compile, needs an implicit conversion:
  val subSpacePositionAsPosition : positionSpace.Position = subSpacePosition
  val subSubSpace = subSpace.appendSubSpace()
  //Does not compile, needs an implicit conversion:
  val subSubSpacePositionAsPosition : positionSpace.Position = subSubSpace
}

Roadblocks I've Encountered So Far

Path-Dependent Types

Part of the problem seems to be that this conversion involves path-dependent types where the input type and the output type uses the same path. However, there is no syntax to write a method with a signature of type x.Position => y.Position where y : PositionSpace and x : y.SubPositionSpace. The problem is easy to solve by adding an additional argument to pass in the path (like below), but you can't add additional arguments to implicit conversion methods (if you want them to be used as such).

//Too many arguments, won't be used by compiler for implicit conversions
implicit def promotePosition(y : PositionSpace, x : y.PositionSpace, x.Position) : y.Position = ...
Nimrand
  • 1,748
  • 2
  • 16
  • 29
  • And for anyone wondering, this isn't strictly academic: I am actually trying to do something useful with this. But, it would be complicated to explain why I'm doing it this way. – Nimrand Nov 23 '13 at 06:09
  • Seriously, why would someone downvote this question? I explained exactly what I'm trying to do with all the pertinent details and provided a compilable code sample that strips out all the unecessary bits (save for the test cases, which is the part I'm trying to get to work). I'm not sure how to simplify the problem down any more without potentially misleading the reader as to what I'm trying to do. The scala experts who know the language inside and out can probably say if this is possible or not, and how, but I don't know how to ask without just literally showing them what I'm trying to do. – Nimrand Nov 23 '13 at 10:58
  • I'm not the downvoter (in fact I just upvoted), but 70 lines of Scala is practically a small novel, and I'm personally very unlikely to work through such a big block of code to try to figure out exactly what you're doing—I find individual methods / code blocks interspersed with explication a lot more approachable. Also I'd guess this could be further reduced. – Travis Brown Nov 23 '13 at 14:02
  • Thank you for the feedback. I'll strip out the methods that aren't core the problem at hand, but I was worried that by doing so, I'd get responses that just try to work around the problem and break all the other code. – Nimrand Nov 23 '13 at 14:09

2 Answers2

1

Here is a partial answer that I came up with and got working. Basically, you create an empty trait, SubPositionSpacePosition, that gets applied to all level-2 positions. You can then define a companion object for SubPositionSpacePosition that defines the implicit conversion. You can only define the conversion for leve-2 positions to level-1 positions. Anything beyond that, and you run into the roadblocks that I outlined in the question.

import scala.language.implicitConversions

class PositionSpace { positionSpace =>

  trait Position extends Ordered[Position] {

  }

  trait SubPositionSpace extends PositionSpace with Position { subPositionSpace =>
    trait SubPositionSpacePosition extends subPositionSpace.Position {
    }

    def append() = new Position with SubPositionSpacePosition { }

    def appendSubSpace() = new SubPositionSpace with SubPositionSpacePosition { }

    class SubPosition(private val subPositionIndex : subPositionSpace.Position) extends positionSpace.Position {
    }

    object SubPositionSpacePosition {
      implicit def promoteSubPositionSpacePosition(subPositionSpacePosition : SubPositionSpacePosition) : positionSpace.Position =
        new SubPosition(subPositionSpacePosition)
    }
  }

  def append() = new Position { }

  def appendSubSpace() = new SubPositionSpace { }
}

object TestCases {
  val positionSpace = new PositionSpace()
  val subSpace = positionSpace.appendSubSpace()
  val subSpacePosition = subSpace.append()
  val subSpacePositionAsPosition : positionSpace.Position = subSpacePosition
  val subSubSpace = subSpace.appendSubSpace()
  val subSubSpacePosition = subSubSpace.append()
  //this still fails to build!!!
  val subSubSpacePositionAsPosition : positionSpace.Position = subSubSpacePosition
}
Nimrand
  • 1,748
  • 2
  • 16
  • 29
0

I don't think you want implicit conversions here—it would be much cleaner just to give the type system the information it needs to keep track of the path-dependent types. One way to do this would be to abstract out a skeleton that ties the types together:

trait PSpace { pSpace =>
  type P
  type Sub <: P with PSpace {
    type P = pSpace.P
    type Sub = pSpace.Sub
  }

  def append(): P
  def appendSubSpace(): Sub
}

You could also use type parameters, but I'd guess type members will be nicer for your use case. Then you provide your implementation:

class PositionSpace { positionSpace =>
  type P = Position
  type Sub = SubPositionSpace

  trait Position

  class SubPositionSpace() extends PSpace with Position {
    type P = positionSpace.P
    type Sub = positionSpace.Sub

    class SubPosition(private val subPositionIndex: P) extends P

    def append() = new Position {}
    def appendSubSpace() = new SubPositionSpace()
  }

  def append() = new Position {}
  def appendSubSpace() = new SubPositionSpace()
}

Now all the lines of your test code will compile just fine.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • That's a very interesting approach. That's not something I'd considered before. However, I think it actually breaks what I'm trying to do. See next comment. – Nimrand Nov 23 '13 at 15:43
  • If I understand correctly, in your example above, the type positionSpace.SubPositionSpace.Position is exactly the same type as positionSpace.Position. However, what I really want is for it to be like a subtype of positionSpace.Position, not the same as, because there are situations where only a positionSpace.SubPositionSpace.Position would be applicable. But, I don't believe I can have an object that is both an instance of positionSpace.Position and positionSpace.SubPositionSpace.Position. Hence, I was trying to do it through implicit conversions. – Nimrand Nov 23 '13 at 15:47