5

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?

Cristian Vrabie
  • 3,972
  • 5
  • 30
  • 49
  • You might be interested in this: http://stackoverflow.com/questions/6156656/how-to-pattern-match-a-class-with-multiple-argument-lists – gzm0 Aug 08 '13 at 15:25
  • This sounds like what GADTs do in Haskell, if you consider the similarity of typeclass contexts to implicits. It might also provide a more principled way to do GADT-like pattern matching in Scala that works nicely. – Mysterious Dan Aug 09 '13 at 13:33

2 Answers2

1

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
       }
     }
  }
}
cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • I did thought that having a second parameter list for case classes is pushing it, but I was trying to make the best use of the implicits. It seems that the execution context is the perfect use-case for using them. It's a value that _just needs to be there_. Your example is the classic way to do it, and maybe I should stick to it. This is basically how it was done in Java for ages (or with _that that must not be named_, ThreadLocal). – Cristian Vrabie Aug 08 '13 at 16:33
0

For the sake of it, I tried to continue with the initial approach to see how far I can take it. What I ended up with might be useful in some cases:

abstract class ContextCommand[T]{
  def context: EC
  def reply(sender:ActorRef)(f: EC => T) = sender.!(
    try f(context)
    catch{
      case e:Throwable => translateExceptionToFailure(e)
    }
  )
}
trait ActorCommons[T]{
  case class GetCommand(val entityId:Long)(implicit val context: EC) 
    extends ContextCommand[Option[T]]
}

then I can use it in the actor as I intended, with the added benefit that the result of the reply function is type-checked.

object MyActor extends ActorCommons[MyClass]
class MyActor extends Actor{
  import MyActor._ 
  def receive = {
    case cmd@GetCommand(entityId) => cmd.reply(sender){ implicit c => onlyAdmins{
      ...
    }}
  }
}
Cristian Vrabie
  • 3,972
  • 5
  • 30
  • 49