[Scala] SOPlurk - Plurk API 2.0 Scala Binding

Introduction

During this Chinese new year vacation, I started a little project that implemented a Scala binding for Plurk 2.0 API to practice how to design a Scala library.

The outcome is SOPlurk, although it was implemented in just 12 days, it should be a complete API binding library. Every API you can find in official document is implemented in SOPlurk, there are even some API that hidden in the Plurk APP console is also being implemented.

Installation

If you're using SBT, it is quite easy to configure it to retrieve SOPlurk library. Just add the following configruation in your build.sbt file.

scalaVersion := "2.10.0"

resolvers += "bone" at "http://brianhsu.moe/ivy"

libraryDependencies += "org.bone" %% "soplurk" % "0.1"

Because SOPlurk used scala.util.Try[T] which only exists on Scala 2.10, so you will need configure SBT to compile your project using Scala 2.10 or above.

Taste

When I designed the SOPlurk API, I've a goal that I want make the API is simple and easy to use, but in the meantime, the client code should be as robust as possible.

After all, this API is sending and getting data from Internet, we will never know what will happen during the connection. Maybe the user's newtwork is dead, the Plurk's server is down, or the data user sent is invalid from Plurk's view.

So the result is we returned almost everything wrapping in a Try[T]. It seems weird at first, but once you get used to it an how it works, it turns out a very easy to use but yet powerful and flexible construct.

For example, the following code is how to use SOPlurk to get my lastes plurk's responses.

import org.bone.soplurk.api.PlurkAPI
import org.bone.soplurk.api.PlurkAPI.PlurkResponses

val appKey = "7xHSzV4xGLJS"
val appSecret = "r2SgShtAqMP0IWdYHQGYyoi8imWqiw48"
val plurkAPI = PlurkAPI.withoutCallback(appKey, appSecret)

for {
  profile <- plurkAPI.Profile.getPublicProfile("brianhsu") // Get my Plurk profile
  plurk   <- Try(profile.plurks.head)  // Get my lastes plurk if there is one
  plurkResponse <- plurkAPI.Responses.get(plurk.plurkID) // Get responses of that plurk
} {

  // ``users`` will be a Map[Long, User], where key is user's userID, and
  // value is user information.
  //
  // ``responses`` is a List of responses in this plurk.
  //
  val PlurkResponses(users, responses, seen) = plurkResponse

  println("Responses:")

  // Print out each response
  responses.foreach { r =>
    val nickname = users(r.userID).nickname
    val qualifier = r.qualifier.name
    val content = r.contentRaw

    println(s"  $nickname $qualifier $content")
  }

}

That's all, the body code block in the for loop will only execute if and only if everything is OK, and we get our data correctly. If there are any exception happens, it will ignore the entire body code block.

Of course this is not a best way to do things, because if there are something went wrong, users will never notice it, the Exception simply being swallowed. But since SOPlurk return almost everything in Try[T], we could choose how to deal it accroding to the situation.

Conclusion

In this practice, things that amazing me most is how powerful and flexible Try[T] is. I've know we could deal Exception by using Either[A, B] for a long time, but Try[T] provides a much better syntax.

Try[T] also has a much more specific semantic, which make using it to note something may go wrong in the API much more attractive compared to Either[A, B].

Once you know how Try[T] works, and how to combine multiple Try[T] to get what you want, the exception handling in program will become easier, and maybe you will fall in love with it just like me. ;-)

回響