Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala implicit extracted values in pattern matching?

We often need to pass through the code context information like the user that is performing the action. We use this context for various things like authorization checks. In these cases implicit values can prove to be very useful to reduce boiler plate code.

Let's say we have a simple execution context that we pass around: case class EC(initiatingUser:User)

We can have handy guards:

def onlyAdmins(f: => T)(implicit context:EC) = context match{
  case EC(u) if(u.roles.contain(Role.ADMIN)) => f
  case _ => throw new UnauthorizedException("Only admins can perform this action")
}

val result = onlyAdmins{
  //do something adminy 
}

I recently found myself in need to do this when working with Akka actors but they make use of pattern matching and I am yet to find a good way of making implicits work well with extractors.

First you would need to pass the context with every command, but that's easy:

case class DeleteCommand(entityId:Long)(implicit executionContext:EC)
//note that you need to overwrite unapply to extract that context

But the receive function looks like this:

class MyActor extends Actor{
  def receive = {
     case DeleteCommand(entityId, context) => {
       implicit val c = context
       sender ! onlyAdmins{
         //do something adminy that also uses context
       }
     }
  }
}

It would be much simpler if extracted variables could be marked as implicit but I haven't seen this feature:

def receive = {
  case DeleteCommand(entityId, implicit context) => sender ! onlyAdmins{
    //do something adminy (that also uses context)
  }
}

Are you aware of any alternative ways of coding this so it reduces the boilerplate code?

like image 431
Cristian Vrabie Avatar asked Nov 13 '22 00:11

Cristian Vrabie


1 Answers

I think the fact that you are adding multiple param sets and implicits to case classes and also having to add a new unapply might be signs that you are going down a not so good path. While these types of things are possible, they are probably not a good idea and maybe something like multiple param sets (and implicits) on case classes could go away one day. I rewrote your example a bit with something more standard. I'm not saying it's a perfect solution, but it's more down the standard path:

trait ContextCommand{
  def context:EC
}

case class DeleteCommand(entityId:Long, context:EC) extends ContextCommand


def onlyAdmins[T](cmd:ContextCommand)(f: => T) = cmd.context match {
  case EC(u) if(u.roles.contain(Role.ADMIN)) => f
  case _ => throw new UnauthorizedException("Only admins can perform this action")    
}

class MyActor extends Actor{
  def receive = {
     case cmd @ DeleteCommand(entityId, ctx) => {
       sender ! onlyAdmins(cmd){
         //do something adminy that also uses context
         //Note, ctx available here via closure
       }
     }
  }
}
like image 161
cmbaxter Avatar answered Nov 15 '22 08:11

cmbaxter