Quantcast
Viewing latest article 3
Browse Latest Browse All 4

Scala Dynamics at Work: A KISSmetrics Library

With Scala 2.10 a pretty cool feature is available by default (it was available as an experimental feature before), namely Scala Dynamics. With Scala Dynamics, you can intercept calls to non existing methods on an object. Consider a class

class A {
    def method(i: Int): String = i.toString
}

and an object of that class:

val x = new A

Now we could call x.method(5) and we would get back the String representation of 5. Now, what would happen if we called x.someOtherMethod(5)? Right, we would get a compile time error, since this method does not exist.

Say Hello to the Dynamic Trait

Scala Dynamics provides you the means to intercept calls to non-existing methods on an object. The call to x.someOtherMethod(5) would therefore not lead to a compile time error, but be dispatched to some generic methods, together with some information about what method was called and what the arguments were. At that point, you are free to do whatever you think is appropriate.

This feature needs to be enabled for a class quite explicitly, by

  • mixing in the Dynamic trait
  • importing the dynamic feature

So, in order to enable this feature for our class from above:

import scala.language.dynamics

class A extends Dynamic {
    def method(i: Int): String = i.toString

    // fill the dynamic dispatch methods in...
}

The dynamic trait defines a number functions that might be implemented in order to intercept certain kinds of method calls. These are (see also the Scala API docs or this blog post):

  • applyDynamic("someOtherMethod")(5): Called for simple method invocations, such as x.someOtherMethod(5).
  • applyDynamicNamed("method")(("high" -> 5)): Called for method invocations involving named parameters, such as x.someOtherMethod(high = "5").
  • selectDynamic("someField"): Called for accesses to an unknown field, e.g., x.someField.
  • updateDynamic("someField")("high"): Called for assignments to an unknown field, such as x.someField = "high".

Now we could extend our class as follows:

import scala.language.dynamics

class A extends Dynamic {
    def method(i: Int): String = i.toString

    def applyDynamic(name: String)(number: Int): Int = name match {
        case "someOtherMethod" => 2 * number
        case _ => throw new RuntimeException(s"Method $name not implemented!")
    }
}

Now, calling x.someOtherMethod(5) will actually do something, while calling something like x.stillNotWorking(5) will throw a RuntimeException. Now lets have a look at how to actually employ this for something useful.

An API for KISSmetrics

KISSmetrics provides user tracking services. This is quite similar to Google Analytics, but KISSmetrics enables you to identify users across different channels. If an anonymous user surfs your website and turns into a customer, both identies are linked. If he logins from another device, that identity is also linked. Using KISSmetrics you have a better idea of how your user behaves on your website, which is important in order to test your existing or new features and optimize your app to provide the best possible user experience.

KISSmetrics provides a very simple REST API, basically allowing you to do 4 things:

  • identify a user, which is exclusively done from JavaScript, since it tells KISSmetrics that the initially anonymous user is now known by some persistent identifier, e.g., the identifier from your database or his email address.
  • trigger an event for a user, such as Signup or Checkout, potentially together with some properties, e.g. number of items checked out.
  • set a property for a user, e.g., gender or country.
  • alias an user with some other ID, e.g., if you have separate services but want to track something accross them.

As I’ve mentioned, the identify actions only makes sense from the UI, but the other actions are often better triggered from the backend, since certain events are more reliably detected there. Especially, if access to a service is provided via native or mobile apps in addition to some website.

A generic implementation of a KISSmetrics library in Scala would probably look something like this:

trait BaseService {
  def alias(person1Id: String, person2Id: String)
  def event(personId: String, eventName: String, properties: Map[String, String] = Map())
  def properties(personId: String, properties: Map[String, String])
}

For instance, in order to trigger a signup event, we could call

service.event("user1", "Signup", Map("Button Color" -> "red"))

While this is works, it would also be cool to do something like

service.signupWithButtonColor("user1", "red")

Normally, we would need to implement some wrapper around the base service, providing such methods, which is rather cumbersome. Scala Dynamics to the rescue! Using the dynamic features of Scala as described above, we can write a service that dispatches certain method calls to the methods of the base service. I published a first version on github. The basic idea is to provide a syntax for the different types of actions supported by KISSmetrics. Currently, the following syntax is supported:

service.e_Signup("user1") // triggering an event
service.e_Signup_with_Button_Color("user1", "red") // triggering an event with properties
service.e_Signup_with_Button_Color_and_Title("user1", "red", "title1") // two properties
service.p_Gender_and_Country("user1", "male", "Germany") // setting a property

The current syntax is rather a proof of concept, to get something implemented that works without too sophisitcated parsing, and there is certainly room for improvement. Currently everything is handled within the applyDynamic method:

def applyDynamic(trigger: String)(personId: String, args: String*) {
    val signature = trigger.split("_").toList
    if (signature.size < 2) throw new IllegalArgumentException("...")
    signature match {
        case "e" :: rest => handleEvent(personId, rest, args)
        case "p" :: rest => handleProperty(personId, rest, args)
        case _ => throw new IllegalArgumentException("...")
    }
}

In handleEvent and handleProperty we further process the signature:

private def handleEvent(personId: String, sig: List[String], propertyValues: Seq[String]) {
    val (propertySignature, name) = parseTo(sig, "with")
    val propertyNames = parsePropertyNames(propertySignature)
    if (propertyNames.size != propertyValues.size) throw new IllegalArgumentException("...")
    val map = propertyNames zip propertyValues toMap;
    event(personId, name, map)
}

The parseTo and parsePropertyNames collect the event name and the property names which are separated by the stop words with and and.

Things to Consider

There are two things to consider when using Scala Dynamics. First of all, there are no compile time checks. You can call arbitrary methods, but if in an unsupported method is called, you will only detect that at runtime. In order to reduce potential harm, you should have good tests in place to account for the missing compile time safety. One exception is the checking of the dynamic method signatures at compile time. The KISSmetrics service expects parameters of type String. Calling service.e_Signup(1) would result in a compile time error.

Secondly, you can not overload the dynamic handler methods. At least, I have not found a way to do that. So, if you want to define event methods that take either a String or an Int person id, you can not do something like:

def applyDynamic(trigger: String)(personId: Int, args: String*) { ... }
def applyDynamic(trigger: String)(personId: String, args: String*) { ... }

Basically, this means you can define exactly one applyDynamic method, which has to handle and further dispatch every legal method invocation you want to support. I assume, in more sophisticated scenarios than the rather simple KISSmetrics API, this results in no compile time checks at all, since the method will probably be defined as

def applyDynamic(sig: String)(args: Any*)

So, the final question is probably, whether using the Dynamic feature of Scala 2.10 is worth the hassle. As far as I know, languages like Ruby have this feature and it is used for ORM mappers such as ActiveRecord or ActiveRDF. They provide a very nice API for accessing data from a database. But one of the reasons to love Scala is probably its type safety, which basically is not present when using the dynamic feature.

I personally think it might still be useful in certain scenarios. The KISSmetrics API is one example. The alternative would be to put the information into a Map, where the same problems exist, i.e., mistyped event or property names, or missed properties. Writing a wrapper with explicit methods for each triggered event gives you great compile time checking, but I personally find it too cumbersome for this use case.

We plan to use the API as soon as doctivity is completely migrated to Scala 2.10, so currently we have no experiences with the library from a production system. I would be interested to hear any opinions on Scala Dynamics or the KISSmetrics library. Do you think it is appropriate to implement a library like that in Scala or is this too “scriptish”?


Viewing latest article 3
Browse Latest Browse All 4

Trending Articles