Abstract Type Members – Augmenting Scala classes
So, Scala’s cool.
About a month ago, a colleague was whining about having to write Java and it was hard not to sympathise. He’s a Python man, and Java’s Kingdom Of Nouns can seem a little clumsy. Sometimes, you just want to map{}, and trying to do that in Java can leave you feeling dirty.
There are proposals for first class functions and closures in Java – Neal Gafter is your man there. They look fun n’all, but they are apparently a little way off. What I needed in order to silence my colleague was less irritating Java in production straight away. In order to satisfy the needs of m’colleague, m’self and m’company I would need a couple of things.
Pour lui
- Concise variable declarations – none of this
Map<String,List<Integer>> map = new HashMap<String, List<Integer>>()
nonsense
- Functions as first class values
- Excellent library support
Pour moi
- Strong, static typing
- Eclipse integration
- Monads. Everybody loves monads
For practicality’s sake
- Maven integration
- Interoperation with the existing Java code base
I had a look on Wikipedia for a suitable JVM language since that would ensure the easy interoperation and give us a decent set of libraries. It turns out Scala has everything we need. It’s mixed paradigm functional / object-oriented, it’s got a Hindley-Milner style type inference system, and it’s got a really active community. The eclipse/maven integration isn’t quite there yet, but it gets much better as you get closer to the bleeding edge.
Some Code
There have been too many cool things to mention really, but here’s the first one that seemed apt for a blog post. Scala has some nice XML support, but lacks some of the functions you might like on an XML Node like, for example, ‘getAttributeValue(attributeName)’. In Haskell, you could define a new typeclass and create an instance for Node. In Java / C++ you could delegate (if you like pain). In Ruby, you could mixin. The answer in Scala appears to be… an Abstract Type Member.
Disclaimer:
I don’t know what the right answer actually is, but this works and seems kinda cool
import scala.xml.NodeSeq
import scala.xml.Node
object XmlTest
{
implicit def nodeToNodePlus(node: Node):NodePlus =
{
return new NodePlus { type T = Node; val init = node }
}
abstract class NodePlus
{
type T <: scala.xml.Node
val init: T
private var value: T = init
def getAttributeValue(name: String): String =
{
return value.attribute(name).get.first.text
}
}
def main(args: Array[String])
{
println("Hello")
val xmlDoc = <hi><hello id="world"/></hi>
val hello = (xmlDoc\"hello").first
println(hello.getAttributeValue("id"))
}
}
The mechanics of what's going on there are... err... pretty opaque to me. But to break that down a little:
- An object is a singleton class, equivalent in most senses to a static class in Java.
- An implicit is a function from A to B which the compiler will use whenever inference tells it that you need a B but have an A (yes, evil, but okay if used carefully).
- type T is an Abstract Type Member. I think.
- <: is a subclass restriction
... all of which allows me to introduce a new function to an existing class without access to that class's constructor. Handy.
That's probably garbage. No doubt I'll find out that's not the way to do it or that it won't do what I expect, but it seems to work. The take-away is that Scala is an easy transition from Java, is much prettier and cooler, and that I can't think of a good reason to write any more Java code.
... having slept on it...
Ah. You see. What I did there was... I got carried away. My NodePlus class is actually no better than a wrapper - I can't actually do any Node operations on it. The implicit mechanism makes it look like I can, but I can't. And the Abstract Type Member was a bit of a red herring. What the Abstract Type Member is doing is letting me parameterise my type which is, I guess, a kind of useful. Given that Scala types accept formal parameters, though, it's not completely clear how much win is involved.
I'll let you know if I find out how to do the thing I actually want to do.
Jorge Ortiz says:
March 12th, 2009 at 7:38 pm
I don’t know what you were trying to do that you didn’t accomplish, but your code is equivalent to the following:
implicit def nodeToNodePlus(node: scala.xml.Node) = new NodePlus(node)
class NodePlus(value: scala.xml.Node) {
def getAttributeValue(name: String): String = value.attribute(name).get.first.text
}
.getAttributeValue(“id”) // == “world”
codders says:
March 12th, 2009 at 8:09 pm
Yep. Sucks.
The plan was to create a value on which I could call ‘getAttributeValue’, but on which I could also call all the existing Node functions (i.e. ‘attribute’). I can’t see a way to do that without manually wrapping each of the functions:
class NodePlus(value: scala.xml.Node) {
def getAttributeValue(name: String): String = value.attribute(name).get.first.text
def attribute(name: String): Option[NodeSeq] = value.attribute(name)
… etc…
}
The issue with that approach is that it’s brittle to changes in the Node class (which would be an issue if Node was something I’d written myself rather than something in the API).