Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I add new methods to a library object?

I've got a class from a library (specifically, com.twitter.finagle.mdns.MDNSResolver). I'd like to extend the class (I want it to return a Future[Set], rather than a Try[Group]).

I know, of course, that I could sub-class it and add my method there. However, I'm trying to learn Scala as I go, and this seems like an opportunity to try something new.

The reason I think this might be possible is the behavior of JavaConverters. The following code:

class Test {
    var lst:Buffer[Nothing] = (new java.util.ArrayList()).asScala
}

does not compile, because there is no asScala method on Java's ArrayList. But if I import some new definitions:

class Test {
    import collection.JavaConverters._
    var lst:Buffer[Nothing] = (new java.util.ArrayList()).asScala
}

then suddenly there is an asScala method. So that looks like the ArrayList class is being extended transparently.

Am I understanding the behavior of JavaConverters correctly? Can I (and should I) duplicate that methodology?

like image 580
Nathaniel Waisbrot Avatar asked Jun 12 '13 17:06

Nathaniel Waisbrot


2 Answers

Scala supports something called implicit conversions. Look at the following:

val x: Int = 1
val y: String = x

The second assignment does not work, because String is expected, but Int is found. However, if you add the following into scope (just into scope, can come from anywhere), it works:

implicit def int2String(x: Int): String = "asdf"

Note that the name of the method does not matter.

So what usually is done, is called the pimp-my-library-pattern:

class BetterFoo(x: Foo) {
  def coolMethod() = { ... }
}

implicit def foo2Better(x: Foo) = new BetterFoo(x)

That allows you to call coolMethod on Foo. This is used so often, that since Scala 2.10, you can write:

implicit class BetterFoo(x: Foo) {
  def coolMethod() = { ... }
}

which does the same thing but is obviously shorter and nicer.

So you can do:

implicit class MyMDNSResolver(x: com.twitter.finagle.mdns.MDNSResolver) = {
  def awesomeMethod = { ... }
}

And you'll be able to call awesomeMethod on any MDNSResolver, if MyMDNSResolver is in scope.

like image 53
gzm0 Avatar answered Sep 24 '22 10:09

gzm0


This is achieved using implicit conversions; this feature allows you to automatically convert one type to another when a method that's not recognised is called.

The pattern you're describing in particular is referred to as "enrich my library", after an article Martin Odersky wrote in 2006. It's still an okay introduction to what you want to do: http://www.artima.com/weblogs/viewpost.jsp?thread=179766

like image 36
Calum Avatar answered Sep 23 '22 10:09

Calum