One of the nice properties of functional programming languages is the easy extensibility of custom functions. Let’s consider a simple F# function (from “FAKE – F# Make”) for a recursive directory copy:
open System open System.IO /// Copies a directory recursive /// Thanks to Robert Pickering http://strangelights.com/blog/ /// param target: target directory : string /// param source: source directory : string let CopyDir target source = Directory.GetFiles(source, "*.*", SearchOption.AllDirectories) |> Seq.iter (fun file -> let newFile = target + file.Remove(0, source.Length) printf "%s => %s" file newFile Directory.CreateDirectory(Path.GetDirectoryName(newFile)) |> ignore File.Copy(file, newFile, true))
If we want to allow users to set custom file filters, we can add a third parameter:
/// Copies a directory recursive /// and allows to filter the files /// Thanks to Robert Pickering http://strangelights.com/blog/ /// param target: target directory : string /// param source: source directory : string /// param filterFile: FilterFunction: string -> bool let CopyDirFiltered target source filterFile = Directory.GetFiles(source, "*.*", SearchOption.AllDirectories) |> Seq.filter filterFile |> Seq.iter (fun file -> let newFile = target + file.Remove(0, source.Length) printfn "%s => %s" file newFile Directory.CreateDirectory(Path.GetDirectoryName(newFile)) |> ignore File.Copy(file, newFile, true))
Now we can define some filter functions:
/// Exclude SVN files (path with .svn) /// excludeSVNFiles: string -> bool let excludeSVNFiles (path:string) = not <| path.Contains ".svn" /// Includes all files /// allFiles: string -> bool let allFiles (path:string) = true
Now it is possible to use CopyDirFiltered in the following ways:
/// Copies all files <=> same as CopyDir CopyDirFiltered "C:\\target" "C:\\source" allFiles /// Copies all files except SVN files CopyDirFiltered "C:\\target" "C:\\source" excludeSVNFiles /// Copies all files only if random number <> 2 let r = new Random() CopyDirFiltered "C:\\target" "C:\\source" (fun path -> r.Next(5) <> 2)
Extensibility of functions in C#
Of course we can do the same thing in C# 3.0:
/// <summary> /// Copies a directory recursive /// and allows to filter the files /// </summary> /// <param name="target">The target.</param> /// <param name="source">The source.</param> /// <param name="fileFilter">The file filter.</param> public static void CopyDirFiltered(string target, string source, Func<string, bool> fileFilter) { string[] allFiles = Directory.GetFiles( source, "*.*", SearchOption.AllDirectories); foreach (string file in from f in allFiles where fileFilter(f) select f) { string newFile = target + file.Remove(0, source.Length); Console.WriteLine("{0} => {1}", file, newFile); Directory.CreateDirectory(Path.GetDirectoryName(newFile)); File.Copy(file, newFile, true); } }
Now it is easy to use the C# function with lambdas:
“A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types.”
[MSDN]
Func<string, bool> filterSVN = x => !x.Contains(".svn"); Func<string, bool> allFiles = x => true; /// Copies all files <=> same as CopyDir CopyDirFiltered("C:\\target", "C:\\source", allFiles); /// Copies all files except SVN files CopyDirFiltered("C:\\target", "C:\\source", filterSVN); /// Copies all files only if random number <> 2 var r = new Random(); CopyDirFiltered("C:\\target", "C:\\source", path => r.Next(5) != 2);
Keeping this simple technique in mind allows to create very flexible functions.
Tags: C#, F#, Fake, Functional Programming