gervais.b
gervais.b

Reputation: 2347

Scala generic types

I there, I'm trying to combine the Command and Chain of responsibility patterns with Scala style. Basically, I would like to have one Executor who pass the command through the chain and return the result. A command is anything that return a T:

class class Invoker {

  type Command[T] = () => T
  
  val handlers = new mutable.ListBuffer[PartialFunction[() => T, T]]

  def invoke[T](command: => T): Future[T] = Future {
    handlers.find(_.isDefinedAt(command)).map(_.apply(command))
  }

}

But, because I'm a scala beginner, I have problems with the generic types for the list of handlers. I cannot figure how to define T in the declaration of handlers so that the invoke command return the correct type (which should be T)

Can someone help me to implement this pattern?

Upvotes: 0

Views: 334

Answers (1)

Sascha Kolberg
Sascha Kolberg

Reputation: 7162

There are several sketchy places, but I think the closest you'll get to what you want would be (Though, it will not work, see below):

import scala.collection.mutable
import scala.concurrent.Future

object Invoker {
  val handlers = new mutable.ListBuffer[PartialFunction[Any, Any]]

  def invoke[T](command: () => T): Future[T] = Future {
    handlers.collectFirst {
      case pf if pf.isDefinedAt(command) => pf(command)
    }.fold(throw new Exception("failure")) {
      _.asInstanceOf[T]
    }
  }
}

However,

  1. It will not work because, the partial functions or more specifically pattern matching will most certainly not work as you expect for Function0

  2. You loose most of your type information through erasure and have to rely upon what you know about the partial functions.

  3. In scala, the need to call asInstanceOf is a good indicator that something can be done better.

@1 When you define a list of partial functions that match on Function0 like for example this:

val T1: () => Int = () => 1
val T2: () => Int = () => 2
val T3: () => Int = () => 3

val pfs: Seq[PartialFunction[Any, Any]] = Seq(
  PartialFunction[Any, Any] {
    case T1 => T1()
  },
  PartialFunction[Any, Any] {
    case T2 => T2()
  },
  PartialFunction[Any, Any] {
    case T3 => T3()
  }
)

Trying to find a match for any function literal will lead to a MatchError:

def invoke[T](c: () => T): T = {
  pfs.collectFirst {
    case pf if pf.isDefinedAt(c) => pf(c)
  }.fold(throw new Exception("failure")) {
    _.asInstanceOf[T]
  }
}


invoke(() => 1) |-> scala.MatchError: <function0> (of class abc.A$A290$A$A290$$anonfun$ti1$1)

This would only work if you define Constants for allowed functions and only use those constants, when you call invoke.

You could define them with the Invoker object:

object Invoker {
  val SomeCommand = () => 5 + 5
  val AnotherCommand = () => 5 * 5

}

But that would utterly eliminate any flexibility.

FINALLY:

If you want to be able to perform any Command then why maintain a list of handlers anyway?

object Invoker {
  def invoke[T](command: () => T): Future[T] = Future {
    command()
  }
}

should be sufficient for that.

Upvotes: 2

Related Questions