How can I add new methods to a library object?
Asked Answered
P

3

7

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?

Pyrogallol answered 12/6, 2013 at 17:59 Comment(0)
M
13

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.

Monarchal answered 12/6, 2013 at 18:11 Comment(2)
Also in 2.10, if it is a conversion you will use frequently and want to avoid the overhead of wrapper allocation, you can make them more efficient by extending AnyVal to make it a value class - docs.scala-lang.org/overviews/core/value-classes.htmlMythicize
But what if you did not create the instance? It is supplied. I can not pimp an existing instance, this only applies for new?Allanite
E
2

The way to do this is with an implicit conversion. These can be used to define views, and their use to enrich an existing library is called "pimp my library".

I'm not sure if you need to write a conversion from Try[Group] to Future[Set], or you can write one from Try to Future and another from Group to Set, and have them compose.

Elzaelzevir answered 12/6, 2013 at 18:6 Comment(0)
C
2

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

Concerted answered 12/6, 2013 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.