Help - Search - Members - Calendar
Full Version: So, Scala is pretty awesome
Forums > Community Center > Technology
mipadi
At work, I'm continuing development of a project called 3ddv, a tool for visualizing data. The program is written in Java (not my choice!). I've grown kind of bored with it and was looking for an opportunity to learn Scala. Scala is an object-oriented functional language that compiles to Java bytecode and thus integrates nicely with Java (and vice-versa). As an example, I rewrote a couple of the existing Java classes in Scala. The results were rather impressive.

I converted these two Java classes:

CODE
package org.nees.rpi.util;

import java.io.File;
import java.util.regex.Pattern;
import java.util.regex.Matcher;


public class StringUtils
{
public static String convertToProperCase(String str)
{
char[] chars = str.trim().toLowerCase().toCharArray();
boolean found = false;

for (int i=0; i<chars.length; i++)
{
if (!found && Character.isLetter(chars[i]))
{
chars[i] = Character.toUpperCase(chars[i]);
found = true;
}
else if (Character.isWhitespace(chars[i]))
{
found = false;
}
}

return String.valueOf(chars);
}

public static String getFileExtension(String filename)
{
String ext = null;
int i = filename.lastIndexOf('.');

if (i > 0 && i < filename.length() - 1)
ext = filename.substring(i+1).toLowerCase();

return ext;
}

public static String getFileNameWithoutExtension(String filename)
{
int i = filename.lastIndexOf('.');

if (i > 0 && i < filename.length() - 1)
return filename.substring(0, i);

return filename;
}

public static String replaceFileExtension(String filename, String newExtension)
{
String oldExtension = getFileExtension(filename);
return oldExtension == null ? filename + "." + newExtension
: getFileNameWithoutExtension(filename) + "." + newExtension;
}

public static boolean isNumber(String s)
{
try
{
Float.parseFloat(s);
}
catch (NumberFormatException e) //not a number, hence the exception
{
return false;
}

return true;
}
}

package org.nees.rpi.validation;


public class Validation
{
public static boolean isPresent(String string)
{
return string != null && string.trim().length() > 0;
}
}


With this Scala class, which is much more concise:

CODE
package org.nees.rpi.util


object StringTransformer {
def toProperCase(s: String) = s.toLowerCase.split("[ _]").map(_.capitalize).deepMkString(" ")

def getBaseName(p: String): String = p.lastIndexOf('.') match {
case -1 => p
case _ => p.slice(0, p.lastIndexOf('.'))
}

def getFileExtension(p: String) = p.lastIndexOf('.') match {
case -1 => None
case _ => Some(p.slice(p.lastIndexOf('.')+1, p.length).toString)
}

def replaceFileExtension(p: String, ext: String) = p.lastIndexOf('.') match {
case -1 => p
case _ => p.split("\\.").reverse.drop(1).reverse.deepMkString(".") + "." + ext
}

def isPresent(s: String) = s match {
case null => false
case _ => s.trim.length > 0
}

def isNumber(s: String) = {
try {
s.toDouble
true
} catch {
case e: NumberFormatException => false
}
}
}


I figured I'd give it a go on another set of classes -- specifically, a hierarchy of shape classes. The results were even better. I turned this set of classes:

CODE
package org.nees.rpi.vis;

import com.xith3d.scenegraph.Geometry;

import javax.vecmath.Point3f;


public abstract class DVGeometry
{
String id = null;

public String getId()
{
return id;
}

public void setId(String id)
{
this.id = id;
}

public abstract Geometry toGeometry(Point3f location);

public abstract Geometry toGeometry(Point3f startPosition, Point3f endPosition);
}

package org.nees.rpi.vis;

import com.xith3d.scenegraph.Geometry;
import org.nees.rpi.vis.viewer3d.GeometryFactory;

import javax.vecmath.Point3f;


public class DVCube extends DVGeometry
{
float size;

public DVCube(float size)
{
super();
this.size = size;
}

public float getSize()
{
return size;
}

public void setSize(float value)
{
size = value;
}

public Geometry toGeometry(Point3f location)
{
return GeometryFactory.createCube(
location.x + size / 2,
location.y + size / 2,
location.z + size / 2,
size
);
}

public Geometry toGeometry(Point3f startPosition, Point3f endPosition)
{
throw new UnsupportedOperationException("end position not does not apply for this shape");
}
}

package org.nees.rpi.vis;

import com.xith3d.scenegraph.Geometry;

import javax.vecmath.Point3f;

import org.nees.rpi.vis.viewer3d.GeometryFactory;


/** A cylinder DVGeometry */
public class DVCylinder extends DVSphere
{
private int defaultDivisions = 30;

public DVCylinder(float radius)
{
super(radius);
}

public Geometry toGeometry(Point3f location)
{
throw new UnsupportedOperationException("not implemented yet");
}

public Geometry toGeometry(Point3f startPosition, Point3f endPosition)
{
return GeometryFactory.createCylinder(startPosition, endPosition, radius, radius, defaultDivisions);
}
}

package org.nees.rpi.vis;

import com.xith3d.scenegraph.Geometry;
import org.nees.rpi.vis.viewer3d.GeometryFactory;

import javax.vecmath.Point3f;


public class DVCuboid extends DVGeometry
{
private float sizex;

private float sizey;

private float sizez;

public DVCuboid(float sizex, float sizey, float sizez)
{
super();
this.sizex = sizex;
this.sizey = sizey;
this.sizez = sizez;
}

public float getSizeX()
{
return sizex;
}

public float getSizeY()
{
return sizey;
}

public float getSizeZ()
{
return sizez;
}

public Geometry toGeometry(Point3f location)
{
return GeometryFactory.createCuboid(
location.x + sizex / 2,
location.y + sizey / 2,
location.z + sizez / 2,
sizex,
sizey,
sizez
);
}

public Geometry toGeometry(Point3f startPosition, Point3f endPosition)
{
throw new UnsupportedOperationException("end position not supported yet for this shape");
}
}

package org.nees.rpi.vis;

import com.xith3d.scenegraph.Geometry;
import org.nees.rpi.vis.viewer3d.GeometryFactory;

import javax.vecmath.Point3f;


public class DVSphere extends DVGeometry
{
float radius = 0.0f;

public DVSphere(float radius)
{
this.radius = radius;
}

public float getRadius()
{
return radius;
}

public Geometry toGeometry(Point3f location)
{
return GeometryFactory.createSphere(radius, 10, location.x, location.y, location.z);
}

public Geometry toGeometry(Point3f startPosition, Point3f endPosition)
{
throw new UnsupportedOperationException("end position does not apply for this shape");
}
}


Into a relatively simple bit of Scala code:

CODE

package org.nees.rpi.vis

import javax.vecmath.Point3f

import org.nees.rpi.vis.viewer3d.GeometryFactory


sealed abstract class DVGeometry(val id: String)

case class DVCube(override val id: String, size: Float) extends DVGeometry(id)
case class DVCuboid(override val id: String, sizeX: Float, sizeY: Float, sizeZ: Float) extends DVGeometry(id)
case class DVCylinder(override val id: String, radius: Float) extends DVGeometry(id)
case class DVSphere(override val id: String, radius: Float) extends DVGeometry(id)

object GeometryRenderer {
def toGeometry(geom: DVGeometry, loc: Point3f) = geom match {
case DVCube(_, s) => Some(GeometryFactory.createCube(loc.x + s / 2, loc.y + s / 2, loc.z + s / 2, s))
case DVCuboid(_, x, y, z) => Some(GeometryFactory.createCuboid(loc.x + x / 2, loc.y + y / 2, loc.z + z / 2, x, y, z))
case DVCylinder(_, r) => None
case DVSphere(_, r) => Some(GeometryFactory.createSphere(r, 10, loc.x, loc.y, loc.z))
}

def toGeometry(geom: DVGeometry, start: Point3f, end: Point3f) = geom match {
case DVCube(_, s) => None
case DVCuboid(_, x, y, z) => None
case DVCylinder(_, r) => Some(GeometryFactory.createCylinder(start, end, r, r, 30))
case DVSphere(_, r) => None
}
}


There were a few other things that impressed me about Scala, many of which I used in the code above. In particular:
  • Case classes let you pattern-match on object types. You can write a function and, based on the object passed in, return different values, all in a very concise bit of code.
  • The Option monad, which is Scala's equivalent of Haskell's Maybe monad, to represent values that may or may not exist. In other words, instead of returning "null" when a function fails (or throwing an exception instead), you can have the function instead return None, indicating no value, or the computed value. This avoids checks for "null" values, and also makes more sense semantically.

The type system/type inference of Scala is pretty cool, too. It has as much -- maybe more -- type safety than Java, but the compiler can figure out a lot of types for you.

In summary, Scala's pretty cool. I wouldn't use it much outside of a Java project, but it's more fun than writing code in Java.
butre
I've always liked Scala. Shit's the shit.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.