Scala: Using functional programming to improve a method

In yesterday’s post titled, How to execute AppleScript from a Java or Scala application, I shared the following Scala method, which isn’t very functional:

object AppleScriptUtils extends Logging {

    /**
     * Executes the AppleScript command you supply as a String.
     * Returns `true` on success, `false` otherwise.
     */
    def executeAppleScriptCommand(cmd: String): Boolean = {
        val scriptEngineManager = new ScriptEngineManager
        val scriptEngine = scriptEngineManager.getEngineByName("AppleScript")
        try {
            scriptEngine.eval(cmd)
            true
        } catch {
            case e: Throwable =>
                logger.error("EXCEPTION in AppleScriptUtils::executeAppleScriptCommand")
                logger.error(e.getStackTrace.mkString("\n"))
                false
        }
    }

}

Before looking at the following text, take a few moments to see what you think is good and bad about that method. I’ll give you a few moments ...

.

.

.

This post is sponsored by my new book,
Learn Functional Programming Without Fear.

The good

From a functional programming (FP) standpoint there are some good things about this code:

  • It does run AppleScript commands solely based on the cmd it’s given
  • It catches its exceptions, and returns a Boolean to indicate whether the command succeeded
  • Except for the logging, it only relies on the cmd value passed into it

The bad

But from an FP standpoint there are also some bad things:

  • It does require that logger, which is set somewhere outside of this method
  • If you really want the method to be reusable, the method should allow a logger to be passed in, and the method should return the Throwable when it fails (rather than throwing an exception)
  • You can’t know this next point unless you’ve worked with the ScriptEngine before, but eval returns an Object, which the user might want

I’ll also add that if you really want to create a reusable utility class, requiring the use of a specific logger is a bad idea. (In my own defense, I started writing an application and then extracted one method into this utility class, and then another, and then another, until I ended up with an AppleScriptUtils class, which I now see will be useful in other projects.)

So from an FP standpoint, this method can definitely be improved. I encourage you to think about how you’d improve this method, or you can just read on ...

A more functional approach

The following code shows an improved version of the same method:

// Version 2
import scala.util.{Try, Success, Failure}

/**
 * Executes the AppleScript command you supply as a String.
 * This method returns a Success(Object) if the command is successful.
 * The Object is whatever is returned by your AppleScript.
 * If the AppleScript command blows up, this method returns a Failure[Throwable].
 * https://alvinalexander.com
 */
def executeAppleScriptCommand(cmd: String): Try[Object] =  Try({
        (new ScriptEngineManager)
            .getEngineByName("AppleScript")
            .eval(cmd)
    })

As you can see, this method uses Scala’s Try/Success/Failure error-handling data types to solve all of the stated problems:

  • It still runs the given AppleScript command
  • It now relies only on the cmd value it’s given
  • On success, it returns the Object to the caller that it gets from the ScriptEngine, wrapped in a Success
  • On failure, it returns the Throwable, wrapped in a Failure

Based on a comment from Adam Rabung, I also modified the code inside the method to eliminate the temporary values I used in the original method.

If you haven’t seen them before, just like Some and None are types of Option, Success and Failure are types of Try.

Still not a “pure function”

Because this method now relies only on the cmd parameter it’s given, it can be reused in other applications. However, we can’t say that it’s a pure function because it communicates with the outside world, and may cause effects in the outside world.

Outside conditions

If you know how AppleScript works with Java/Scala, you can also argue that you’ll get different exceptions depending on the conditions in which the method is run -- for instance, if you try to run it on Linux or Windows -- but I think that specific point is trivial. The object name (AppleScriptUtils) should give away the point that it’s meant to be used on Mac OS X systems.

That being said, there is a real problem here for Mac OS X systems: The way you execute AppleScript from Scala/Java is different on Java 6 than it is on Java 7 or 8. If I ever thought about trying to run this code using Java 6, it won’t work. Or, more accurately, it will throw a Failure[Throwable]. (I could fix the code to account for Java 6, but since I don’t have any plans to go backwards, I won’t do that.)

Make it more FP-like?

Because this method has side effects, I know that experienced FP programmers will want to do some things to it to make it more “FP-like”. (If you are that person, feel free to show an improved version of the code in the Comments section below.)

Myself, I haven’t seent the need to go that far with FP yet. Because I I can tell by its name that this method is going to have side effects, that’s all I need to know, I’m good with that.

That being said, it might be cool if Scala had an annotation to mark methods that have side effects, maybe something like this:

@SideEffects
def executeAppleScriptCommand(cmd: String): Try[Object] ...

But that annotation doesn’t exist, and since I’m okay with knowing by looking at its name that this method has side effects, I’ll stop modifying my method here.

About the @SideEffects annotation

A few notes about that annotation idea:

  • To be clear, I just made up that @SideEffects annotation. It’s not in the Scala language.
  • I have no idea whether anyone else in the world wants that annotation. (I saw a comment where Martin Odersky said that they have talked about adding something like this to Scala, but could never agree on a good way to implement it. His comments made me think that an annotation like this would help my own thinking.)
  • If you like the idea, you can always create your own annotation. As a benefit, it will make you think twice about writing a method with side effects, and it will also make methods that have side effects easier to find.
  • As a pitfall, it does require the developer to mark his/her methods like this. (I don’t know how to enforce something like this with a compiler. But again, if 99% of methods that have side effects are marked like this, it will make them easier to find.)

I did see some notes where people talked about having a “pure” annotation, but in my 80/20 world, I think 80% of methods should be functional, and 20% impure, so I’d rather mark the 20% than the 80%.

Summary

In summary, I hope this has been a decent, “real world” example of how to make a “normal” method much more functional. Although I’ve written it quickly, I hope I showed what was bad about the original code, and why the new code is better, and more reusable.