Friday 7 June 2013

Fun With Scala/Play, Part 1

I currently have a rather useful little app running which I call PingCaster. Basically when it receives an HTTP GET it triggers a whole lot of other HTTP GET requests to a list of configured URLs. I'll leave it to you to consider possible applications of this...
It's actually one of the first toy apps I wrote using Play! Here is version 1 of Application.scala:
...

import org.apache.http.client.fluent.Request
import org.apache.http.StatusLine

object Application extends Controller {

  abstract trait Addressable { def getAddress():String }

  trait Pingable extends Addressable {
    def ping() : StatusLine= {
      println("Pinging '" + getAddress() + "'")
      val result = Request.Get( getAddress() ).execute().returnResponse().getStatusLine()
      println("Pinged '" + getAddress() + "' - got result: '" + result + "'")
      result
    }
  }

  case class PingTarget(url:String)  extends Addressable with Pingable {
    override def getAddress():String = {
      url
    }
  }

  def sendPing = Action {
    val pingTargets =
      PingTarget("http://www.fake1.net") ::
      PingTarget("http://www.fake2.com") ::
      PingTarget("http://www.fake3.com") ::
      PingTarget("http://subdomain.fake4.com") ::
      Nil

    val results = pingTargets.map( pt => (pt.url, pt ping))
    Ok(html.ping(results))
  }
}
I was pretty happy with that; I was using a couple of Scala features, namely the use of a trait, a case class, cons-style list construction, the map operation and returning a tuple (containing the URL being pinged and the result of the ping) from it. It was also fun using the new (for me) fluent interface to the Apache HttpClient. And most importantly, it worked!
But this is Scala. I'm surely not done yet, right? Let's try and make this a bit more idiomatic ...
Firstly, the construction of all those PingTarget objects is very repetitive, let's do it just-in-time:
def sendPing = Action {
    val pingTargets =
      "http://www.fake1.net" ::
      "http://www.fake2.com" ::   
      "http://www.fake3.com" ::   
      "http://subdomain.fake4.com" ::   
      Nil

    val results = pingTargets.map( pt => (pt, PingTarget(pt) ping))
    Ok(html.ping(results))
  }
Actually, let's use an implicit conversion to hide PingTarget from the client entirely:
implicit def str2PingTarget(value: String) = PingTarget(value)

...

    val results = pingTargets.map( pt => (pt, pt ping))
And actually, we can drop the whole PingTarget thing and get the converter to create us an anonymous type:
  implicit def str2Pingable(value: String) = {
    new Addressable with Pingable {
        override val getAddress =  value 
    } 
  }

OK. But we're still executing those pings sequentially! How quaint! In the next installment, let's get parallel...

No comments:

Post a Comment

Comments welcome - spam is not. Spam will be detected, deleted and the source IP blocked.