This blog explores Scala dynamic types, a new feature of Scala 2.10, and provides some hopefully interesting exercises so you can try it out for yourself.
If you use Java or Scala instead of, say, Python or Clojure, it's probably
because you like compile-time type checking. Personally, if I make a sloppy
error, I prefer to have the bad news up front than discover it later through
tedious debugging. Still, it's hard not to be envious when I see features such
as Ruby's ActiveRecord ORM that peeks at the database and automatically makes
classes out of tables, turning column names into field names. Without any
boilerplate, the Ruby programmer can write empl.lname = "Doe"
.
I realize it's not like solving world hunger. In Java and Scala, I can
perfectly well write empl.set("lname", "Doe")
. But still, I am
envious.
Scala, never a language to leave an enviable feature on the table, gives you
that syntactic sugar in version 2.10. Specifically, when you
call empl.lname = "Doe"
, the compiler converts it to a call
empl.updateDynamic("lname")("Doe")
.
Of course, that's only permitted for certain types—otherwise, Scala stay statically typed.
Dynamic types must extend the marker trait scala.Dynamic
. You
must also add the following line to the class that defines a dynamic type:
import scala.language.dynamics
(This is another part of Scala 2.10—if you use an obscure language
feature, you need to tell the compiler with a special import
directive. Users of dynamic types don't need to do this, just the
implementors.) Finally, you implement one or more
methods selectDynamic
, updateDynamic
(for dynamic
fields) applyDynamic
or applyDynamicNamed
(for
dynamic methods). Here, a dynamic field is a field whose name can be arbitrary
(such as the column name of a database object). Similarly, a dynamic method has
an arbitrary name that gets resolved at runtime—an example is in the next
section.
Here are the details. Consider obj.name
, where obj
belongs to a class that's a subtype of Dynamic
. Here is what the
Scala compiler does with it.
name
is a known method or field of obj
, it
is processed in the usual way.obj.name
is followed by (a1, a2, ...)
,
n=v
),
pass the arguments on to applyDynamic
:
obj.applyDynamic("name")(a1, a2, ...)
applyDynamicNamed:
obj.applyDynamicNamed("name")((n1, v1), (n2, v2), ...)
Here, n1
, n2
, and so on, are strings with
the argument names, or ""
for unnamed arguments.
obj.name
is to the left of an =
, call
obj.updateDynamic("name")(rightHandSide)
obj.selectDynamic("sel")
There are some gory issues with type parameters and sequence arguments—see the spec proposal for that.
So, if we wanted to build an ActiveRecord-like ORM, we'd produce a class
Active
with methods selectDynamic
and
updateDynamic
(which take care of obj.name
as an
rvalue and lvalue), similar to the usual apply
and
update
methods for lists and maps.
One of the most popular features of my book Scala for the Impatient are the exercises, so here's one:
Exercise 1: Implement an object CSV
so that
you can do the following:
val empl = CSV.findById("Employee", 42) // Read Employee.csv // First row has column labels; one of them must be id println(empl.lname) empl.lname = "Doe" CSV.save("Employee")
Just like a real ORM, you need to cache the objects. Each object belongs to
some class (such as CSV
) with fields for the table name and a hash
map. The selectDynamic
and updateDynamic
read and
update the map values.
Exercise 2. It would be nicer if objects from different
tables belonged to different classes, like with the Ruby ORM. You can't imitate
this exactly in Scala because class creation isn't a runtime event. How close
can you come? The class user will define something like class Employee extends
CSV
. You need to find a way to read the proper CSV file into a pool of
Employee
objects, and save them later. Call getClass
?
Use generics? Your choice.
Ruby has an XML builder that lets you generate XML like this:
xml.date { xml.year "2006" xml.month "01" xml.day "01" } # Yields <date><year>2006</date><month>01</month><day>01</day><date>
Hashes are used for attributes:
xml.hello("World!", "type" => "global") # Yields <hello type="global">World!</hello>
Can we imitate this in Scala? As in Ruby, let's make the tag names dynamic method calls on the builder object. We can use a variable sequence of pairs for the attributes.
xml.img("src" -> "http://horstmann.com/cay-tiny.gif", "alt" -> "a tiny image of the author")
In Ruby, any (!) method can have an optional block argument, but in Scala, it's not easy to have an optional block after a variable argument list. I worked with Martin Christensen, a visiting scholar in our department, explored various syntactical possibilities. He settled on having the attributes after the element content.
He implemented a method
def applyDynamic(name: String)(block: => Any, args: (String, Any)*): Any
so that you can generate XML like this:
val searchLinks = List( "Google" -> "http://www.google.com", "Ask.com" -> "http://www.ask.com", "Yahoo!" -> "http://www.yahoo.com", "Alta Vista" -> "http://www.altavista.com") val xml = new XmlBuilder xml html { xml head { xml title "Search Links" } xml body { xml p ("Search Links:", "style" -> "color: red") xml ul ({ for ((name, link) <- searchLinks) xml li { xml a (name, "href" -> link) } }, "id" -> "searchList") } } println(xml.getXml)
All those xml
inside are a little unsightly, but you can't
avoid them. A dynamic method must be called on a dynamic object. We tried using
an anonymous subclass of a dynamic class, hoping that the dynamic methods would
be called on this
, but it didn't work.
Exercise 3: Implement the XmlBuilder
class.
Hint: In the applyDynamic
method, concatenate the start tag and
attributes to a mutable String
field, call the block and
concatenate its result if it's a string, then concatenate the end tag. The
getXml
method just returns that field.
Exercise 4: Make the attributes come first. Use Currying to separate the attribute list from the block. Use an empty default for the block.
Exercise 5: Generate genuine Scala XML (i.e. a
scala.xml.Node
, not a String
).
Of course, we aren't proposing this as a replacement for the native Scala XML syntax. It's just an example to get you thinking about dynamic methods.