user8162258
user8162258

Reputation:

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

Upvotes: 1

Views: 49

Answers (2)

Alexey Romanov
Alexey Romanov

Reputation: 170733

it complains that method run overrides nothing

That's because overriding method must have the same type parameters, you just replace I and C, so in this case it must be

new Node[Int,Any] {

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

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

Note that you this signature says "caller can get State[O] for any O <: Int it wants", e.g. Nothing. You can't really meaningfully implement a method with this signature.

If you want to get this signature for run, then in Node it probably should be

protected def run(item: I, context: C, tick: Int): State[I]

which means I can't be contravariant (or covariant, for that matter).

Upvotes: 2

Wellingr
Wellingr

Reputation: 1191

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

Upvotes: 1

Related Questions