As I noted in the first of these articles, I plan to dive right in rather than teach Scala from scratch as if to new programmers. So I'll begin with a translation of a simple, real-world development problem from Java to Scala, just to give the flavor of the language and highlight a few key capabilities & differences from Java.
The problem, taken from an old unit test suite, is to generate a web application directory on the fly for use by an embedded servlet container. We'll focus on one aspect, the creation of web.xml
. Our code will allow a test suite to specify servlet context parameters, servlet classes and URL bindings, and return the appropriate web.xml
file as a string. Here's how it's used:
WebXmlGen gen = new WebXmlGen();
gen.addParam("initConfig", "WEB-INF/myconfig.xml");
gen.addServlet("main", "myapp.tests.Mainervlet").bind("/");
gen.addServlet("test", "myapp.tests.TestServlet").bind("/test");
new FileWriter("WEB-INF/web.xml").write(gen.webXml());
For the Java code, we'll need a list to hold the servlet configurations, another list for the context parameters, and helper classes for each we'll call ParamInfo
and ServletInfo
.
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import org.jdom.DocType;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Text;
import org.jdom.output.XMLOutputter;
public class WebXmlGen1
{
private List<ServletInfo> servlets = new ArrayList<ServletInfo>();
private List<ParamInfo> params = new ArrayList<ParamInfo>();
The webXml
method will use JDOM for generating the XML, and will delegate to XML-generating methods in ParamInfo
and ServletInfo
:
public String webXml() throws IOException
{
Element root = new Element("web-app");
Document doc = new Document(root);
doc.setDocType(new DocType("web-app",
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd"));
for (ParamInfo ps : params)
ps.addXml(root);
for (ServletInfo ss : servlets)
ss.addServletXml(root);
for (ServletInfo ss : servlets)
ss.addMappingXml(root);
XMLOutputter xo = new XMLOutputter();
StringWriter output = new StringWriter();
xo.output(doc, output);
return output.toString();
}
The helper class for holding parameter information is straightforward, just a place to stash the parameter name & value. Its addXml()
method adds the <context-param>
element to the JDOM Document.
private class ParamInfo
{
String name;
String value;
ParamInfo(String name, String value) {
this.name = name;
this.value = value;
}
void addXml(Element root) {
Element paramName = textElement("param-name", name);
Element paramValue = textElement("param-value", value);
Element param = new Element("context-param")
.addContent(paramName).addContent(paramValue);
root.addContent(param);
}
}
Given this definition, here is the addParam
method:
public void addParam(String name, String value) {
params.add(new ParamInfo(name, value));
}
Next, there is the ServletInfo
class; it holds the name and class of the servlet, a list of URL patterns to map to the servlet:
private class ServletInfo
{
String name;
String klass;
List<String> patterns = new ArrayList<String>();
ServletInfo(String name, String klass) {
this.name = name;
this.klass = klass;
}
void addServletXml(Element root) {
Element servletName = textElement("servlet-name", name);
Element servletClass = textElement("servlet-class", klass);
Element servlet = new Element("servlet").addContent(servletName)
.addContent(servletClass);
root.addContent(servlet);
}
void addMappingXml(Element root) {
for (String pattern : patterns) {
Element servletName = textElement("servlet-name", name);
Element urlPattern = textElement("url-pattern", pattern);
Element mapping = new Element("servlet-mapping").addContent(
servletName).addContent(urlPattern);
root.addContent(mapping);
}
}
}
Here is the addServlet
method. To avoid exposing the ServletInfo
class to callers, we define an interface that supports the bind()
method shown earlier, and return an anonymous class instance that implements the interface.
public interface UrlBinder {
UrlBinder bind(String pattern);
}
public UrlBinder addServlet(String name, String klass) {
final ServletInfo info = new ServletInfo(name, klass);
servlets.add(info);
return new UrlBinder() {
public UrlBinder bind(String pattern) {
info.patterns.add(pattern);
return this;
}
};
}
Finally, there is a convenience method for building elements containing a single text node.
private Element textElement(String name, String text) {
return new Element(name).addContent(new Text(text));
}
Easy enough. Let's begin the translation and introduce some of Scala's improvements. Here are the first few lines of the class as expressed in Scala:
import java.io.StringWriter
import scala.collection.mutable.ListBuffer
import scala.xml.XML
import scala.xml.dtd.{DocType, PublicID}
class WebXmlGen2
{
private var servlets = new ListBuffer[ServletInfo]()
private var params = new ListBuffer[ParamInfo]()
The class definition is familiar enough; it works the same way as in Java, however the default modifier is public. As you also may have guessed, type parameterization uses square brackets, not angle brackets, so you write ListBuffer[ParamInfo]
rather than ListBuffer<ParamInfo>
.(ListBuffer
is one of Scala's collection classes; you can also use java.util
collections.)
What's clearly missing here is the "double declaration" boilerplate found in Java. Because of Scala's type inferencing, you needn't declare the type of a field or method if Scala can intuit it from an initializer or a return value; just use "var
" or "val
" to indicate whether the field is mutable or not (can change after assignment.)
Also missing are explicit line terminators; it's hardly ever necessary to put a semicolon in Scala code.
Next we'll translate the addParam
and addServlet
methods. These also are straightforward:
def addParam(name: String, value: String) =
params += new ParamInfo(name, value)
trait UrlBinder {
def bind(pattern: String): UrlBinder
}
def addServlet(name: String, klass: String) = {
val info = new ServletInfo(name, klass)
servlets += info
new UrlBinder {
def bind(pattern: String) = {
info.patterns += pattern
this
}
}
}
New concepts:
- Names and types in declarations are reversed, a la "name: type" rather than "type name".
- Instead of interfaces, Scala uses traits, which can express both function contracts and behavior. More on this in another post; in this case, our use of a trait is the same as the use of an interface in Java.
- Functions are introduced using the
def
keyword. Braces are optional around functions because the body of a function is simply an expression, and in Scala a block of statements is also an expression. It can be used anywhere, not just for lexical structuring purposes as in Java. The value of a block expression is the value of the last contained expression, so a return statement is optional in this case.
Note again that we haven't declared the return type of these functions: Scala has inferred them from the return values. OK, nothing too revolutionary so far; next we redo the webXml()
method:
def webXml = {
val nodes =
<web-app>
{params.map(p => p.toXml)}
{servlets.map(s => s.servletXml)}
{servlets.map(s => s.mappingXml)}
</web-app>
val out = new StringWriter()
XML.write(out, nodes, "utf-8", true,
DocType("web-app", PublicID(
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd"), Seq()))
out.toString()
}
Whoa... this is different. Lines 38-41 are just serialization code from Scala's standard XML library, but what about lines 32-36?
This is Scala's support for XML literals at the language level. Writing XML from Scala means... just writing XML. The compiler checks the XML for well-formedness and converts it to an internal runtime representation. You can interpolate application data in elements or attributes using curly braces, as shown here.
Inside the braces, we see the use of function literals, a great source of flexibility that was regrettably, along with closures, dropped from Java 7. Function literals allow function definitions to be used as values and passed around just like instances of built-in and user-defined types. The syntax of function literals is
(arg1: type, ..., argN: type) => expression
Parentheses may be left off the arg list if there is only one argument whose type can be inferred. So the expression
params.map(p => p.toXml)
is the same as
params.map((p: ParamInfo) => p.toXml)
because Scala knows the argument list of a function passed to ListBuffer[T].map
is of type (T)
. What this says, then, is "Take each element of params
, apply the toXml
method, and return a sequence of the results." Since the type of each sequence member is in turn inferred to be an XML node (because ParamInfo.toXml
returns XML) Scala will add those elements as children of the <web-app>
element; likewise with the XML for the servlet definitions.
Continuing on, you will now probably not be surprised by the definition of the ParamInfo
class:
private class ParamInfo(name: String, value: String)
{
def toXml =
<context-param>
<param-name>{name}</param-name>
<param-value>{value}</param-value>
</context-param>
}
Or maybe you will be... where's the constructor? It's created automatically by appending an argument list to the class name as shown. More boilerplate gone; no need to populate member fields by hand.
Finally, there's the updated ServletInfo
, and no new surprises this time:
private class ServletInfo(name: String, klass: String)
{
val patterns = new ListBuffer[String]()
def servletXml =
<servlet>
<servlet-name>{name}</servlet-name>
<servlet-class>{klass}</servlet-class>
</servlet>
def mappingXml =
patterns.map(pattern =>
<servlet-mapping>
<servlet-name>{name}</servlet-name>
<url-pattern>{pattern}</url-pattern>
</servlet-mapping>)
}
What have we accomplished so far with Scala? We've eliminated useless boilerplate at several points, because the Scala compiler will intuit information that the Java compiler forces the programmer to specify. We've used expression-oriented syntax to describe operations on collections more concisely than in Java. We've generated XML in the most natural way possible: using XML syntax, instead of through an API. We've reduced the code size by about a third.
Best of all, the result of compiling the Scala version is plain old Java bytecode. The Scala version of WebXmlGen
can be instantiated from Java, deployed in a JAR, debugged in Eclipse, or profiled for performance tuning.