Skip to content

Using Scala Functions to Simplify Code

November 6, 2010

I’ve recently been diving into Scala to create Scarab, a simulation framework I’m developing.  Coming from a Java / Groovy background, I was still in the habit of using classes and inheritance to create functionality.  However, as I was documenting code, I decided it would be cleaner to use functions and pass them as parameters.

One of the things I find useful is to have the ability to write output to different output streams in different formats.  I separated the code to do the formatting from the code to do the writing so that I could easily mix and match writers and formatters.  Originally both the writers and the formatters inherited from a base class.  Writers would then add additional functionality to write and formatters would add additional formatting capabilities.

The base writer contained the following methods (plus more that aren’t relevant to this post):

abstract class AbstractWriter {

  private var formatter : TraceFormatter = SimpleFormat

  // formats, then writes the message
  def trace (trace: String, message: String) = { write (formatter.format(trace, message))  }

  // sets the formatter to use.
  def withFormat (format : TraceFormatter) = {
    formatter = format
    this // for method chaining
  }

  // abstract method writers should implement.
  protected def write (message: String)

}

The base formatting class was defined as such:

abstract class AbstractFormatter {
  // method to be overridden by the formatter to do the formatting.
  def format(trace : String, message: String) : String
}

// Class to format with trace type and date for searching.
class LogFormatter extends AbstractFormat {
  def format(trace: String, message: String) = { "[" + trace + "] [" + new Date() + "] " + message }
}

So, I happily began creating classes to do formatting, but realized that my users might want to create their own formats as well.  Initially I simply said to extend the base formatter and use that with the writer.  However, this has the potential to clutter up the class space and each formatter is at least three lines.  While users could use anonymous classes, it just didn’t feel right.  All I wanted was a function to format a string.

So, after reviewing my Programming In Scala book, I figured out how to make the changes.  First, I changed the AbstractWriter to the following:

abstract class AbstractWriter {

  // Assigns an initial function for the formatter.  The type is implicitly determined.
  private var formatter = TraceFormatters.SimpleFormat

  // formats, then writes the message
  def trace (trace: String, message: String) = {    write (formatter(trace, message))  }

  // sets the formatter to use.  The parameter is now a function.
  def withFormat (format : (String, String) => String) = {
    formatter = format
    this
  }

  // abstract method writers should implement.
  protected def write (message: String)

}

Now, the defined formatters are just functions I placed inside an Object:

object TraceFormatters {

  val SimpleFormat = (trace: String, message: String) => message
  val LogFormat = (trace: String, message: String) => "[" + trace + "] [" + new java.util.Date() + "] " + message

}

I can easily add additional formats as needed with a single line of code.

The major benefit, however, is that now users can create formats on the fly.  Lets say that a user wants to create a formatter that formats messages as comma separated values, then all that is needed is the following:

// ignoring the case of messages or trace names with commas.
val writer = new FileWriter("log.csv") withFormat { _ + ", " + _}

Switching from classes to functions makes the code more readable, reduces the number of classes that need to be created, and provides increased flexibility for users of my class.

 

From → Programming, Scala

4 Comments
  1. Pretty neat. The equivalent in groovy would be a closure?
    Also it sounds like you just reinvented something like log4j.

    • Thanks. I believe you could do the same thing with closures in Groovy. It’s probably a common pattern that’s worth being comfortable with if one comes from an OO background.

      This came about because log4j didn’t do quite what I wanted it to. It turns logging on and off based on the log level, i.e. info, warning, etc. I wanted to turn it on and off based on the types of messages I wanted to see and have some finer grained control over the tracing, i.e. I want to see all messages at all levels, but only for process X and Y, not Z. I even created a writer that uses log4j to log. I find the tracing more effective for debugging and verification of complex interactions.

      I’m considering spitting this out into a separate project from my main one since I find the capability useful independent of the simulation framework this was built to support.

  2. I received the following from a colleague, Oscar Renalias, and am posting it here with permission.

    Instead of encapsulating your formatters into a Scala object, you could also define them as classes or objects that extend Function2[String, String, String] that implement the apply() method. The good thing about these objects is that they look and act like functions if you want them to, but they may also act as “real” classes providing extra methods and functionality.

    The code for the formatter would be something like this:

    class SampleFormatter extends Function2[String,String,String] {
    def apply(a:String, b:String) = // logic that returns String
    // … more functions and attributes
    }

    Creation of your writer doesn’t look very different:

    val writer = new FileWriter(“log.csv”) withFormat (new SampleFormatter)

    Now SampleFormatter here is acting like a function even though it’s a class instance. You could also define it as an object so that you can skip the “new” part. Additionally, if SampleFormatter had more methods than just apply (e.g. unapply for pattern matching), you could use the same class/object for all purposes.

    I think this is pretty cool 🙂

    • This approach is more elegant than the original, but I feel it suffers from similar problems. For each new formatter I have to create a class. Since I don’t currently expect any of the formatters to be used for anything else, then the ability to add other functionality isn’t useful. However, I’ll certainly keep this open as an option for the future should that case arise.

Leave a reply to billdback Cancel reply