First Class Function Example in Scala and Go
Jan 20 2014Go and Scala both make functions first-class citizens of their language. I recently had to recurse a directory tree in Go and came across the Walk function which exemplifies first-class functions. The Walk function talks a path to start a directory traversal and calls a function WalkFunc for everything it finds in the sub-tree:
func Walk(root string, walkFn WalkFunc) error
If you’re coming from the Kingdom of Nouns you may assume WalkFunc is a class or interface with a method for Walk to call. But that cruft is gone; WalkFunc is just a regular function with a defined signature given its own type, WalkFunc:
type WalkFunc func(path string, info os.FileInfo, err error) error
Why is this interesting? I wasn’t surprised Go would have a built-in method for crawling a directory tree. It’s a pretty common scenario, and I’ve written similar code many times before. What’s uncommon about directory crawling is what you want to do with those files: open them up, move them around, inspect them. Separating the common from the uncommon is where first-class functions come into play. How much code have you had to write to just write the code you want?
Scala hides the OOP-ness of its underlying runtime by compile-time tricks, putting a first-class function like:
val walkFunc = (file: java.io.File) => { /* do something with the file */ }
into a class of Function1. C# does something similar with its various function classes and delegate constructs. Go makes the interesting design decision of forcing function declarations outside of structs, putting an emphasis on stand-alone functions and struct extensibility. There are no classes in Go to encapsulate functions.
We can write a walk method for our walkFunc in Scala by creating a method which takes a function as a parameter (methods and functions have nuanced differences in Scala, but don’t worry about it):
object FileUtil { def walk(file: File, depth: Int, walkFunc: (File, Int) => Unit): Unit = { walkFunc(file, depth) Option(file.listFiles).map(_.map(walk(_, depth + 1, walkFunc))) } }
In our Scala walk function we added a depth parameter which tracks how deep you are in the stack. We’re also wrapping the listFiles method in an Option to avoid a possible null pointer exception.
We can tweak our walkFunc and use our Scala walk function:
import FileUtil._ val walkFunc = (path: File, depth: Int) => { println(s"$depth, ${path}") } walk(new File("/path/to/dir"), 0, walkFunc)
Because typing (File, Int) => Unit is somewhat obscure, type aliases come in handy. We can refactor this with a type alias:
type WalkFunc = (File, Int) => Unit
And update our walk method accordingly:
def walk(file: File, depth: Int, walkFunc: WalkFunc): Unit = { ... }
First class functions are powerful constructs making code flexible and succinct. If all you need is to call a function than pass that function as a parameter to your method. Just as classes have the single responsibility principle functions can have them too; avoid doing too much at once like file crawling and file processing. Instead pass a file processor call to your file crawling function.