The previous post demonstrates my thought process for solving problems, enhanced with the aid of the F# Interactive (FSI) Window. The programming exercise I used was presented at August’s F# SIG in Cleveland. The exercise was to recursively act on a directory with different operations: get the total size of all the files, delete all the files, move all the files, etc. In the previous post, I skipped over the “move files” part of the problem because it introduced a new concept that I felt would have detracted from the focus of the post. This post completes the exercise, introducing partial function application to solve .

Here’s the higher-order function that we came up with last time:

1
2
3
4
5
6
7
8
9
open System.IO

let actOnFiles directory fileOp =
    let rec getFilesHelper files dir =
        Directory.EnumerateDirectories(dir)
            |> Seq.collect (fun s -> getFilesHelper files s)
            |> Seq.append (Directory.EnumerateFiles(dir))

    getFilesHelper Seq.empty directory |> fileOp

The fileOp argument takes the function that will perform an operation on each file. Here’s an example:

1
2
let getSize files =
    files |> Seq.map (fun f -> (new FileInfo(f)).Length) |> Seq.sum

The signature for fileOp is (seq<string> -> 'a). The parentheses tell us that the argument is a function. The type after the last arrow is the return type. 'a means that the function will return a generic type. For example, getSize returns int64, and delete returns unit (kind of like void in C#). The leftmost part of the signature tells us the types of the arguments to the function. fileOp takes one argument: a sequence of strings.

So here’s the problem with copyFiles, and why I didn’t include it in the last post: in addition to the files to copy, we need to also provide a destination directory for them, and the root path of the source directory1. This makes the function signature different from what actOnFiles expects. Fortunately, functional languages (including F#) have an eloquent way to solve this problem!

The technique is called Partial Function Application2. The idea is that you create a new function by “fixing” one or more of the first arguments of another function. Here’s a classic example:

1
2
3
4
5
let add x y = x + y
add 2 4 // = 6

let add5 = add 5
add5 3 // = 8, and is equivalent to saying add 5 3

You can see how we created a new function, add5, by fixing the value 5 as the first argument to add.

So let’s get a look at the copyFiles function:

1
2
3
4
5
6
7
8
9
let copy (source:string) (destination:string) files =
    Seq.iter (fun (f:string) ->
        let dir = Path.GetDirectoryName(f.Replace(source, destination))
        match Directory.Exists(dir) with
            | false -> ignore(Directory.CreateDirectory(dir))
            | true -> ignore()

        File.Copy(f, Path.Combine(dir, Path.GetFileName(f)))
    ) files

This function has the signature string -> string -> seq<string> -> unit, but we need for it to look like seq<string> -> 'a. Here’s how we can do that using Partial Function Application:

1
2
let copyPartial files = copy "C:\Source" "C:\Destination"
actOnFiles "C:\Source" copyPartial

What we are doing is supplying the first two arguments to create a function with the signature we need for passing into actOnFiles. To put it another way, we are taking a function with the signature string -> string -> seq<string> -> 'a, pre-wiring the two string parameters so that we don’t need to supply them, leaving us with a function that has the signature seq<string> -> 'a. Perfect!

In the above example, I explicitly bound the new function to an identifier. This isn’t necessary, as the following code shows:

1
actOnFiles "C:\Source" (copy "C:\Source" "C:\Destination")

Note the use of the parentheses and the absence of the files argument, which was necessary earlier because F# couldn’t infer the type correctly3.

That’s it! Here’s the code for the exercise in its entirety:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
open System.IO

let actOnFiles directory fileOp =
    let rec getFilesHelper files dir =
        Directory.EnumerateDirectories(dir)
            |> Seq.collect (fun s -> getFilesHelper files s)
            |> Seq.append (Directory.EnumerateFiles(dir))

    getFilesHelper Seq.empty directory |> fileOp

let print files =
    files |> Seq.iter (fun f -> ignore(printfn "%s" f))

let getSize files =
    files |> Seq.map (fun f -> (new FileInfo(f)).Length) |> Seq.sum

let copy (source:string) (destination:string) files =
    files |> Seq.iter (fun (f:string) ->
        let dir = Path.GetDirectoryName(f.Replace(source, destination))
        match Directory.Exists(dir) with
            | false -> ignore(Directory.CreateDirectory(dir))
            | true -> ignore()

        File.Copy(f, Path.Combine(dir, Path.GetFileName(f)))
    )

let delete files =
    files |> Seq.iter (fun f -> File.Delete(f))

actOnFiles "C:\Temp" print
actOnFiles "C:\Temp" getSize
actOnFiles "C:\Temp" (copy "C:\Temp" "C:\Temp3")  //Note the non-DRYness in "C:\Temp"... :(
// actOnFiles "C:\Temp" delete //DO AT YOUR OWN RISK!!!

1 The source argument seemed to be a necessary evil so that I knew the "root" of where the copy was to take place. You will see a subtle form of duplication in the overall code which I'm not crazy about. I welcome suggestions. 2 Recently, I have heard that Partial Function Application and Currying are two different terms. This [article](http://mikehadlow.blogspot.com/2010/04/currying-vs-partial-function.html) may shed some light. I said "may"... 3 Without the files argument, I was getting this compiler error: "Either make the arguments to 'copyPartial' explicit or, if you do not intend for it to be generic, add a type annotation." Help to resolve this would be appreciated.