Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get defined parameters for partial function

Say I have a partial function (similar to Akka's receive method for actors)

def receive: PartialFunction[Any, Unit] = {
    case SomeCaseClass(params) => println("whatever")
}

Is there any way to get all the defined parameters of this function?

I'm implementing an JSON RPC like service structure. I basically want clients to be able to define a service via a partial function.

def clientService = {
    case Connect(login, password) =>.....
    case SomeMessage => ...
    case SomeMethod(bla) => ..
}

e.g. the first method would be translated from

{method: "connect", params:{login: "asdsad", password: "adsada"}}

(this part works already)

Now if a client has defined a service and another client wants to know about the available methods of the service, I now need to know what kind of case classes the service accepts so I can tell the requesting client. I know I could do this easily with normal methods in an object, but due to how I parse JSON and translate it into case classes the partial function would simplify my API and I like beautiful code ;)

Plus I'm pretty sure there has to be a way via reflection, although I don't know how partial functions are represented after compiling / at runtime.

like image 372
rawphl Avatar asked Oct 05 '22 03:10

rawphl


1 Answers

Depending on how many assumptions you can or are willing to make about your services, here is a totally unsophisticated approach that might nevertheless be an option. It basically relies on 1) all possible message types being known and b) the partial functions being partial only in one dimension (more on that later).

We need a finite set of possible message types:

sealed trait Message

case class Hello(who: String) extends Message
case class Lunch(withWhom: String) extends Message
case class Dinner(withWhom: String) extends Message
case class Goodbye(who: String) extends Message

And a few example services:

val service0: PartialFunction[Any, Unit] = {
  case Hello(who) => ()
  case Goodbye(who) => ()
}

val service1: PartialFunction[Any, Unit] = {
  case Hello(who) => ()
  case Lunch(withWhom) => ()
  case Goodbye(who) => ()
}

var services = List(service0, service1)

Next, we also define a list of message instances that are meant to serve as blueprints for accepted messages:

val simpleInstances = List(Hello("*"), Lunch("*"), Dinner("*"), Goodbye("*"))

Finally, we define a method that returns accepted arguments from a partial function and a list of possible arguments:

def supportedArguments[F, T, A <: F]
                      (pf: PartialFunction[F, T], args: Iterable[A]) =

  args filter pf.isDefinedAt

A pretty printer:

def printSupportedArguments(services: Iterable[PartialFunction[Any, Unit]],
                            messages: Iterable[Message]) {

  services.zipWithIndex.foreach {case (s, i) =>
    val supported = supportedArguments(s, messages)
    println(s"Service $i supports $supported")
  }
}

Let's go:

printSupportedArguments(services, simpleInstances)
  // Service 0 supports List(Hello(*), Goodbye(*))
  // Service 1 supports List(Hello(*), Lunch(*), Goodbye(*))
  // Service 2 supports List(Goodbye(*))

Things become more complicated if the services are not only partial with respect to the message type they accept, but also partial with respect to the message content they accept, that is, if they are partial in more than one direction:

val service2: PartialFunction[Any, Unit] = {
  case Hello("Thomas") => ()
  case Hello("Laura") => ()
  case Goodbye(who) => ()
}

Such a service also requires an adaptation of the list of blueprint instances:

val moreInstances = simpleInstances ++ List(Hello("Thomas"), Hello("Laura"))

Resulting in:

printSupportedArguments(services :+ service2, moreInstances)
  // Service 0 supports List(Hello(*), Goodbye(*), Hello(Thomas), Hello(Laura))
  // Service 1 supports List(Hello(*), Lunch(*), Goodbye(*), Hello(Thomas), 
  //                         Hello(Laura))
  // Service 2 supports List(Goodbye(*), Hello(Thomas), Hello(Laura))

The set of drawbacks of this approach obviously includes the following:

  • Message types must be known

  • If services are partial in multiple dimensions, the all possible message content must be known as well

  • Using the instance Hello("*") to denote arbitrary instances of Hello makes it impossible to distinguish between services that accept Hello(_) and services that literally only accept Hello("*")

Should you find a real solution using macros or reflection (or magic), please post it here!

like image 141
Malte Schwerhoff Avatar answered Oct 11 '22 20:10

Malte Schwerhoff