The combination of a functional language and an interactive interpreter has helped me think differently about solving problems. The functional language makes me write expressions - code that returns an answer; and the interactive window lets me test those expressions. Since it tends to be easier to test small things in isolation, I naturally end up with many small functions that work together to solve larger problems. I like to call these small functions Building Blocks.
Here’s an example of the process using the coding problem from Cleveland’s first F# User Group meeting.
Step 1: Break the problem down
|
|
Easy enough. Just a few comments to guide my way. When I initially do this I express the objectives at whatever level of detail I can comprehend at that time. As I begin coding I can sometimes break an objective down into smaller objectives. Keep this in mind while you are coding. It will have an impact in your problem solving skills and in the shape of your finished code.
Step 2: Start implementing
|
|
Do one thing at a time and just enough to complete your objective. Sound familiar?
All I did here was just enough to make sure that I could find all of the files in a given directory. I used the F# Interactive window to print out the files in order to ensure that my code was doing what I expected. Since the printing code seemed simple enough and I had evidence that it worked from using FSI I went ahead and kept that code too.
Step 3: Set up the building blocks
|
|
You might call this a refactoring. I simply extracted the two things my code is doing into their own functions; and called them. You can start to see a program taking shape!
Step 4: Do the recursion
WARNING! There is an explanation of the code beneath the example. If you are new to F# then you may want to gloss over the code and then follow along. In the next code example things will become much neater.
|
|
We all know that file systems are tree structures and you can get all the files from a node by using recursion1. What you see here is just a first pass at getting the code working properly. There are two important things to notice at this stage:
- The way the recursion works. I first get the subdirectories and then use
Seq.collect
to callgetFilesHelper
for each subdirectory. The result ofgetFilesHelper
is a sequence of file names so each folder has the files appended into one large sequence. - The use of a “helper” function. The
getFilesHelper
function takes an argument that collects the results of each of the recursive calls. By creating this function as a helper inside the maingetFiles
function we can spare the calling code those details; and avoid the smell of an output parameter.
Step 5: Clean up
|
|
Pipelining to the rescue! This is much more readable! The getFilesHelper
function now reads what it does: Enumerate the directories collect the files in each directory and append the results together2. This is the part of F# that makes me smile!
Step 6: Make it generic functional
|
|
Here’s the beauty of functional languages! We created a higher-order function called actOnFiles
(fka: getFiles
) and pass print
into it. Now we have a function that takes two arguments: a directory and a function to apply to each file in that directory and its subdirectories. Here are two more higher-order functions to demonstrate the point
|
|
So that’s it! By focusing on small problems we have achieved function composition to solve a larger problem; and in doing so have actually made our code more extensible. By using the interactive window I could test small portions of the code (enumerating files directories determining how to find the file size etc.) before putting them to use in the larger context of the problem.
Stepping back all we really did was employ a basic problem solving skill: Breaking a problem down into manageable pieces and solving each piece one at a time. Imagine that!
2 I do not believe that this recursive call uses [tail recursion](http://blogs.msdn.com/b/fsharpteam/archive/2011/07/08/tail-calls-in-fsharp.aspx) because the result of the recursive call is used when being appended to our "collector" variable.