Pages

2012-02-03

Akka Untyped Actors & Google Guice

I started using Akka Actors in my Scala project and wanted to make them play nice with Google Guice.

Akka Actors are STRICTLY (I don't think you understand how strict) enforced to be instantiated in a certain way.  So I went on a trek to do this.

First off, Akka Actors are wrapped in an ActorRef. This ref has no reference to the underlying Actor type. An ActorRef is an ActorRef is an ActorRef. So to differentiate them you must use annotations. Not wanting to make a new annotation for each I went with the provided @Named annotation. This leads to a not so terrible injection of:

class WeaveCompiler @Inject() ( @Named("WeaveActor") val weaveActor:ActorRef ) {

That's all fine and dandy, and satisfies the first half of the binding:

bind(classOf[ActorRef].annotatedWith( Names.named("WeaveActor") )

But now we need our ActorRef. We can't let Guice do it for us since we have an Actor and it needs an ActorRef. Time for a provider. I didn't want to have to make a provider for every single Actor so I made it generic. Java type erasure quickly bites us in the ass but there are ways around that there are over my head. In short, the following works:

.toProvider( new TypeLiteral[ActorProvider[WeaveActor]]() {} )

So TypeLiteral is provided by Guice and takes care of that type erasure headache. The ActorProvider is my own special glue that I will provide below and WeaveActor is my Actor that extends akka.actor.Actor

So here is my glue:

import com.google.inject._
import akka.actor._

class ActorProvider[T <: Actor] @Inject() ( val m:TypeLiteral[T], val injector:Injector ) extends Provider[ActorRef] {
  def get = {
    Actor.actorOf( injector.getInstance( Key.get( m ) ) ).start
  }
}

Because of how akka actors work the Actor object MUST be created inside a call to actorOf. Fail to do that and you get a horrible exception.  Thus the provider gets the TypeLiteral for the actor injected with the Injector from Guice. Then uses those to get an instance of the Actor from the Injector inside the call to actorOf. Then it starts the actor and returns the ActorRef.

This solution seems simple, but I spent the better part of 4 hours to figure it out.  There is probably still something missing (I haven't checked what happens if you inject the same actor in multiple places) but that can be an exercise for the reader. If I find any problems I'll make another post about it.

Hope this helps!

2012-02-01

Tip: Guice Multibinder & Scala Set


I am using Guice Multibinder with Scala and this took me way too long to think up. To make things nice I want Scala Sets not Java Sets. So the following is a nice way to do it, make a constructor with Java types and call the one with Scala types.

import scala.collection.mutable.{Set => MutableSet}
import scala.collection.JavaConversions._

class MyClass( val t:MutableSet[T] ) {
@Inject() def this( t:java.util.Set[T] ) = this( asScalaSet(t) )

//...

}

2012-01-19

Parboiled, Case Classes and Headaches

I had the lovely task today of figuring out how to not use case classes in Scala for my AST.  My AST demanded as structure that resulted in conflicts of overriding constructor parameters when case classes were used. This meant I needed to build the class myself, and it took all day to get it right. My first attempt was the following as I found a blog post stating that Parboiled only depends on the apply method:

object ImportViral {
  def apply( packagespec:PS ):ImportViral =
    new ImportViral( packagespec )
}
class ImportViral( packagespec:PS ) extends ImportStatement( packagespec ) {}


And my respective rule in the parser:

def ZImportViral = rule { "importviral" ~ WhiteSpace ~ ZPS ~~> ast.ImportViral }

But this resulted in a rather ugly error:

error: overloaded method value ~~> with alternatives:

[X, Y, Z, R](f: (X, Y, Z, ast.PS) => R)org.parboiled.scala.rules.ReductionRule3[X,Y,Z,R] and

[Y, Z, R](f: (Y, Z, ast.PS) => R)org.parboiled.scala.rules.ReductionRule2[Y,Z,R] and

[Z, R](f: (Z, ast.PS) => R)org.parboiled.scala.rules.ReductionRule1[Z,R] and

[R](f: ast.PS => R)org.parboiled.scala.rules.Rule1[R]

cannot be applied to (net.codingwell.weave.languages.silk.ast.ImportViral.type)

I figured out pretty quick that the issue was f: ast.PS => R meaning it wanted some sort of conversion from my PackageSpecification to "R" (ImportViral). So obviously I did something wrong. I fiddled for half the day before trying hardcoding the rule type.

def ZImportViral:Rule1[ast.ImportViral]

This gave me a new only minorly more helpful error.

found   : net.codingwell.weave.languages.silk.ast.ImportViral.type (with underlying type object net.codingwell.weave.languages.silk.ast.ImportViral)

required: net.codingwell.weave.languages.silk.ast.PackageSpecification => net.codingwell.weave.languages.silk.ast.ImportViral

So again, my object isn't a conversion. After another several hour sprint trying to figure out what a case class is equivalent to in code. I found a decompiler http://java.decompiler.free.fr/?q=preview
Plugged in a case class from my AST and looked for differences. Took me a little bit to notice but plain as day, the object was inheriting from Function1. The magical glue to make a function object in scala. Now I am no Scala expert so this also taught me that objects can inherit from different things than their companion class. Who Knew?

Final working code:

object ImportViral extends Function1[PS,ImportViral] {
   def apply( packagespec:PS ):ImportViral =
     new ImportViral( packagespec )
}
class ImportViral( packagespec:PS ) extends ImportStatement( packagespec ) {}


This code can be found on github: Github - Deathbobomega/Weave - ast.scala