Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can’t Scala infer the path of a path-dependent type—even from an explicit self-reference?

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.

like image 955
Ben Kovitz Avatar asked Jul 13 '15 09:07

Ben Kovitz


1 Answers

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

like image 150
dk14 Avatar answered Nov 20 '22 20:11

dk14