Search code examples
scalagenericscontravariance

difficulty when using contravariant type parameters for both a type parameter and return type of a method


I am attempting to build a behaviour tree library in scala but I am having trouble with the variance. The following is a simplified version of a Node:

sealed trait State[+O]
case class Running[+O](curOut: O, node: Node[O,_]) extends State[O]
case class Success[+O](out: O) extends State[O]
case object Failure extends State[Nothing]

trait Node[-I,C] {
    protected def canRun(item: I, context: C, tick: Int): Boolean
    protected def run[O <: I](item: I, context: C, tick: Int): State[O]

    def apply[O <: I](item: I, context: C, tick: Int): State[O] = {
        if (canRun(item, context, tick)) run(item, context, tick)
        else Failure
    }
}

whenever I try to instantiate Node however, it complains that method run overrides nothing:

val node = new Node[Int,Any] {

    override protected def run(item: Int, context: Any, tick: Int): State[Int] = ???

    override protected def canRun(item: Int, context: Any, tick: Int): Boolean = ???
} 

I have tried changing O in Node to a type member but that complains that Covariant I is used in contravariant position:

type O <: I

What I am trying to ask is how can I use a contravariant type parameter as the type for a method parameter and for a method return type? Ideally I would not like to have to make I contravariant for reusability purposes in parent and decorator nodes.

Thanks in advance


Solution

  • To answer your question

    how can I use a contravariant type parameter as the type for a method parameter and for a method return type?

    You can't, a conta- (or co)-variant type parameter cannot be used in both a method parameter and the return type.

    My recommendation to work around this is to either remove the node parameter from the Running case class. Or to replace the parameter with a read-only trait. For example:

    case class Running[+O](curOut: O, process: Process[O]) extends State[O]
    trait Process[+O] {
      def currentState: State[O]
    }