This blog is no longer being maintained, please go to The missing link of Agile instead

Monday, June 29, 2009

Design patterns in Scala: Command

Many people say that Command pattern in languages of functional paradigm is not really a pattern but actually an inherent feature of a language itself. I don't know about other languages but in Scala it is not entirely true. While it is rather quite easy to come up with Command pattern implementation - you write a closure and it's done ;) - but variety of possibilities that language gives you is pretty amazing.

Let's start from the very beginning.
I believe that the three following points represent the most important aspects to consider when choosing appropriate implementation of Command Pattern in Scala.
  • what the signature of "Command" used as a parameter looks like
  • how do you construct a "Command" instance
  • how do you assign a "Command" instance to a value
The most straightforward solution to Command pattern implementation is:

// Example 1
def command(i : Int) = println(i);

// Usage:
// 1
def invoker(cmd : Int => Unit) = cmd(1)
// 2
invoker(command _)
// or
invoker(command)
// 3
val savedCommand = command _

However concise and elegant it has important shortcomings. Firstly you get very wide and crude interface - (Int) => (Unit) means nothing after all. You won't communicate a lot of information with that and a user can pass any command to an "invoker" method (including ShutDownReactor procedure ;]). The second thing is a nasty wildcard you have to use when assigning an instance of a command to a variable. (which you may optionally use in 2. - and if you don't compiler will do it for you).

You can have a bit cleaner solution with following piece of code.

// Example 1
def command = (i : Int) => println(i);

// Usage:
// 1
def invoker(cmd : Int => Unit) = cmd(1)
// 2 a) prepared command
invoker(command)
// 2 b) ad hoc command
invoker((i : Int) => println(i))
// 3
val savedCommand = command

The method presented above is actually the partially applied function of a function from the previous example. You gain the advantage of not writing the '_' (placeholder) when assigning an instance to a variable - what IMHO both simplifies and makes it a little less magical :). And if you wonder what would happen if you've added it anyway, the answer is simple - you'd get a partially applied partially applied function ;P with following signature:

() => (Int) => Unit

To solve the issue of wide interface you might use the more classical example of a Command pattern with a bit of functional language coolness on the top ;)

// Example 3
trait Command { def apply(i : Int) }
object DefaultCommand extends Command { def apply(i : Int) = println(i) }

// Usage:
// 1
def invoker(cmd : Command) = cmd(1)
// 2
invoker(DefaultCommand)
// 2 b) ad hoc command construction
invoker(new Command { def apply(i : Int) = println(i) })
// 3
val savedCommand : Command = DefaultCommand

Pretty nice - it gives you very clean nice interface with possibility of extending it with whatever responsibility you like (what was obviously missing in first two examples). Nothing comes for free though - the ad hoc construction of a command class is more verbose than a simple command function.

If you're cra^M^M^Mbrave enough you might even try something like this:

class CrazyCommand extends (Int => Unit) { def apply(i : Int) = println(i) }

And now the nice thing is that you may both use it as in 3. example and also pass it to a function defined in previous code snippets. Though I have no idea why anyone would like to do that ;)

While I'm at it I'd remind you about a cool Scala feature that let you extend class behaviour at instantiation and thus you can do the same with:

new Command with (Int => Unit) { def apply(i: Int) = println(i) }

Fun Exercise! ;)

type CoolCommand = (Int => Unit) with (String => Unit)

def coolFunction(cmd : CoolCommand) { cmd(1); cmd("one") }

The implementation of a class that may be passed as a parameter to a 'coolFunction' is left to a reader and may be impossible :P

No comments:

Post a Comment