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, buteval
returns anObject
, 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 theScriptEngine
, wrapped in aSuccess
- On failure, it returns the
Throwable
, wrapped in aFailure
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%.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
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.