I'd like to pass an object to a function that accepts an argument with a projected type, and get Scala to deduce that the object's type comes from the object that encloses it. Here's some simple code to illustrate the difficulty:
trait Cult { cult_ =>
case class CultLeader(personality: Personality) {
val cult = cult_
val follower = personality.attractFollower(this)
}
case class Follower(leader: CultLeader, name: String)
}
trait Personality {
def attractFollower(leader: Cult#CultLeader) =
leader.cult.Follower(leader, "Fred") // <-- THIS LINE FAILS TO COMPILE
}
In other words, a CultLeader’s Personality should attract a Follower to the same Cult as the CultLeader.
The Scala 2.11.2 compiler says:
TypeProjection.scala:11: error: type mismatch;
found : Cult#CultLeader
required: leader.cult.CultLeader
leader.cult.Follower(leader, "Fred")
^
It compiles and runs correctly if I add a cast, like this:
leader.cult.Follower(leader.asInstanceOf[leader.cult.CultLeader], "Fred")
That seems clumsy and it introduces run-time checking for something that ought to be deducible at compile-time. At least I have a workaround. How can I get the Scala compiler to deduce that leader
's type is in fact leader.cult.CultLeader
?
I'd prefer not to pass cult
as another argument to attractFollower
. In my actual code, that could result in a lot of ugly passing around of the cult
parameter—when it really shouldn't need to be passed at all.
The simple way is:
trait Cult { cult_ =>
case class CultLeader(personality: Personality) {
val cult = cult_
val follower = personality.attractFollower(this)
}
case class Follower(leader: Cult#CultLeader, name: String) // <-- Cult#CultLeader here
}
trait Personality {
def attractFollower(leader: Cult#CultLeader) =
leader.cult.Follower(leader, "Fred")
}
// Exiting paste mode, now interpreting.
defined trait Cult
defined trait Personality
Here you're explicitly specifying that Follower
can take any projection, which you're actually trying to force with asInstanceOf
.
There is another way to do that:
trait Cult {
case class CultLeader(personality: Personality) {
def fl(name: String) = Follower(this, name)
val follower = personality.attractFollower(this)
}
case class Follower(leader: CultLeader, name: String)
}
trait Personality {
def attractFollower(leader: Cult#CultLeader) = leader.fl("Fred")
}
or
trait Cult {
case class CultLeader(personality: Personality) { ld =>
val follower = personality.attractFollower(this)
case class Follower(name: String) { val leader = ld }
}
}
trait Personality {
def attractFollower(leader: Cult#CultLeader) = leader.Follower("Fred")
}
UPDATE: This example could make it more clear why Scala is doing what she's doing:
trait Cult { cult_ =>
case class CultLeader(personality: Personality) {
val cult: Cult = cult_ //could be new Cult{} as well
val l = this.asInstanceOf[cult.CultLeader] //We have to do asInstanceOf here because Scala have no glue (from type signature) that this cult is same as cult_
val follower = personality.attractFollower(this)
}
case class Follower(leader: CultLeader, name: String)
}
trait Personality {
def attractFollower(leader: Cult#CultLeader) =
leader.cult.Follower(leader.l, "Fred")
}
// Exiting paste mode, now interpreting.
defined trait Cult
defined trait Personality
And here is final solution which does what you want in correct way:
trait Cult { cult_ =>
case class CultLeader(personality: Personality) {
val cult: cult_.type = cult_
val l: cult.CultLeader = this
val follower = personality.attractFollower(this)
}
case class Follower(leader: CultLeader, name: String)
}
trait Personality {
def attractFollower(leader: Cult#CultLeader) =
leader.cult.Follower(leader.l, "Fred")
}
// Exiting paste mode, now interpreting.
defined trait Cult
defined trait Personality
The catch here is that cult_.type
is path-dependent (projected).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With