Codemountain, Paulo Suzart's Blog

Scala: Implicits, Options and Pattern Match to start with RabbitMQ

with 3 comments

update: link to code, now using sxr 0.2.1

Hi folks. Long time away! Confess I hadn’t had nice ideas to work on, but now I have a lot.

While implementing one of them (sort of Scala lib to use Actors as RabbitMQ Queue consumers over the Java API), I figured that my lib user shouldn’t explicitly instantiate or even interact with some original RabbitMQ Java API classes. This small piece of my construct is sufficient to show nice Scala features and functional programming style (the following is not the unique solution or the best way to do that, but funny and teaching).

To consume a Queue, we  first connect to the broker and get a Connection and a Channel. Using this ConnectionFactory constructor is useful to get a Connection. However, it takes a set of parameters wrapped in ConnectionParameters class.

So I decided follow these rules:

1. lib users must not instantiate ConnectionParameters directly

2. configuration must appear in the order: user, password, virtual host

3. configuration must take one line and be transparent to lib client

4. Ah! Users shouldn’t use common Scala Lists

How to make it happens in Scala? My answer was put some interesting features together such as Pattern Match, Options, Case Classes and Implicit Conversions.

Using a SBT aproach to declare project dependencies, We have the format:


val connParams = "guest" % "guest" % "/" //say a abstract val named connParams

An implicit function will help to convert a String to an object that takes a String value and the previous declared parameter as arguments. So we get  Param(“/, Param(“guest”, Param(“guest”, None))). Every time the user call the function % on a String, this implicit converter will instantiate a new Param.

The convertion function can be done as:

 implicit def s2Param(s : String) = Param(s, None)

Here a new element takes place, the Param class hasn’t a new keyword. That is a Case Class, a especial Class that help us with PatternMatch and  doesn’t need such a keyword.  And more, the parameters of a case class are “mapped to” attributes accessible by its own names:

case class Param(value : String, precending : Option[Param])

So, say a variable named x referencing Param, x allows x.value and x.precending without any extra declarations.🙂

s2Param implicitly converts a String to a Param Instance to bring the function %. Let see the function %:


case class Param(value : String, preceding : Option[Param]) {

def % (value : String) = Param(value, Some(this))

}

For our surprise, we have one more element here, the Some –  in conjunction with None (see s2Param) – extends Option[+A]. Option is used to represent optional values, and here is a nice place to apply it. For example, the preceding Param of a Param is not required for the first in the chain.

None means no value, and Some means some value of the type of +A. Option has many other features not covered here.

Now, with the class designed, we can add another function (used strictly by the lib) to transform the parameter chain into the Rabbit ConnectionParameters:


def asConnectionParams = { 

 val (user, pass, vHost)  = this match {
   case Param(user, None) =>  (user, ConnectionParameters.DEFAULT_PASS, ConnectionParameters.DEFAULT_VHOST)
   case Param(pass, Some(Param(user, None))) => (user, pass, ConnectionParameters.DEFAULT_VHOST)
   case Param(vHost, Some(Param(pass, Some(Param(user, None))))) => (user, pass, vHost)
   case _=> error("ConnectionParameters configuration Failed")
 }

 //Returning ConnectionParametters
 new ConnectionParameters {
   setUsername(user)
   setPassword(pass)
   setVirtualHost(vHost)
 }

 }

asConnectionParams uses Pattern Match to declare the val user, pass and vHost matching the current instance of Param (supposed to be the las declared by the user) against some expected patterns. This Pattern Match carres about the default values to configure the ConnectionParametters instance returned. Any other Param combination not allowed will fall into case _, and a error is raised.

Now the Param class complete:


   case class Param(value : String, preceding : Option[Param]) {

      require(value != null)  

      def % (value : String) = Param(value, Some(this))

      def asConnectionParams = { 

        val (user, pass, vHost)  = this match {
           case Param(user, None) =>  (user, ConnectionParameters.DEFAULT_PASS, ConnectionParameters.DEFAULT_VHOST)
           case Param(pass, Some(Param(user, None))) => (user, pass, ConnectionParameters.DEFAULT_VHOST)
           case Param(vHost, Some(Param(pass, Some(Param(user, None))))) => (user, pass, vHost)
           case _=> error("ConnectionParameters configuration Failed")
          }

        //Returnin ConnectionParametters
        new ConnectionParameters {
        	setUsername(user)
        	setPassword(pass)
        	setVirtualHost(vHost)
        }

      }
    }

Usage:


val params = "guest" % "guest" % "/"

Thus, the super class can execute params asConnectionParams to grab the params required to get a Connection and a RabbitMQ channel.

To make things simple to test by yourself, you can find the code that runs stand alone here (with Scala X-Ray). Next time I promess a Scalatest or something like.

Thats it! Impressive? No! Useful? Maybe. Funny? For sure!!!

Stay up and see more POSTs about real RabbitMQ and Scala soon. It was an appetizer to play around with Scala.

Thanks for coming!

Written by paulosuzart

agosto 1, 2009 às 7:52 pm

Publicado em RabbitMQ, scala

Tagged with ,

3 Respostas

Subscribe to comments with RSS.

  1. Nice use of implicit! And I’m glad to see sbt’s % operator is spreading around.

    Have you tried sxr 0.2.1? It can export iframe tags to embed the source right in a web page.

    n8han

    agosto 7, 2009 at 3:52 pm

  2. […] é um objeto que extends a classe org.specs.Specification. Specification oferece um conjunto de conversores implícitos para Strings, por isso os métodos should, in e >> aparentam serem invocados a partir das […]

  3. […] voce observar bem neste Post, eu criei o operador % aplicável a Strings para facilitar a configuração de alguns parâmetros […]


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: