I constantly hear people say that F# is a cool cutting-edge technology, but they do not know how to use it. In previous chapters, I showed how C# developers can pretty much map their existing imperative, LINQ, and data-structure knowledge to F#. However, this is not enough know-how to design or implement a component or a system. In this chapter, I use well-known design patterns to introduce performing system design by using F#. The samples in this chapter use unique F# language features to implement well-known design patterns. These samples will help you start to think of F# as something other than a niche language.
I do not see a huge difference between computer language and human language. Both languages are used to convey human thinking, only the audiences are different. One is the computer, and the other is a human. If you want to master a language and use it to write a beautiful article, having knowledge of only the basic words of that language would definitely not be enough. Likewise, if people really want to use F# fluently in their daily programming work, they need to know more than how to write a float type and a FOR loop.
In this chapter, a number of design patterns are implemented in F#. These implementations should help you gain more insight about how our team designed the language and, consequently, how to use these features to solve system-design problems. Ultimately, my goal is to help you start to really think in F# terms.
There are some design patterns that are easily implemented with more advanced F# language features, such as F# object expressions. I am not going to discuss every aspect of these features. More detailed information about these special language features will be presented in Chapter 5. If any aspects of this chapter are not clear, I encourage you to refer to Chapter 5, where F# unique features are introduced in detail.
Like many well-studied concepts, design pattern has many definitions. In this book, I borrow the definition from the Wikipedia page on the topic (http://en.wikipedia.org/wiki/Software_design_pattern). My quick definitions of the design patterns in this chapter are also largely based on Wikipedia.
The design pattern is the reusable solution template for a problem. It can speed up the development process by providing tested, proven development paradigms. The effective software design requires considering problems that may not become obvious until later in the implementation. Reusing design patterns helps to prevent subtle issues that can cause major problems, and it also improves code readability for coders and architects who are familiar with the patterns.
From the preceding statements, you can see that design patterns are not necessarily tied to specific languages or programming paradigms. Given that the object-oriented programming (OOP) paradigm is the most used, most design-pattern implementations and discussions are based on languages that target OOP—for example, C#. Some people from the functional programming community have suggested that design patterns are merely a means to address flaws in OOP languages. I will not go into the details of this topic; instead, I will cover how to use F# to implement design patterns.
First, I’ll cover three basic concepts in programming languages that primarily target OOP:
Encapsulation is a construct that facilitates the bundling of data with methods (or other functions) that operate on that data.
Inheritance is a way to compartmentalize and reuse code. It creates a subtype based on an existing type.
Polymorphism: subtype polymorphism, which is almost universally called just polymorphism in the context of object-oriented programming, is the ability to create a variable, a function, or an object that has more than one form.
The typical C# implementations of design patterns often use all three of these concepts. In the rest of the chapter, you will see how F# can use both OOP and functional features to implement most common design patterns.
Before demonstrating these design patterns, I’d like to remind you that a design pattern can have more than one implementation. Each of the implementations in the following examples show different F# language features in practice. Additionally, they provide a better way to apply F# in component or system design than what would be achieved by simply porting over a C# implementation.
Let’s start by looking at some of the design patterns that will be discussed in this chapter along with the definitions of each. Note that the following definitions are from an OOP perspective, so the definitions occasionally still use object-oriented terminology:
The chain of responsibility pattern avoids coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. It chains the receiving objects and passes the request along the chain until an object handles it.
The decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
The observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
The proxy pattern provides a surrogate or placeholder for another object to control access to it.
The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it.
The state pattern allows an object to alter its behavior when its internal state changes.
The factory pattern lets a class defer instantiation to subclasses.
The adapter pattern and bridge pattern are both used to convert the interface of a class into another interface. The adapter pattern lets classes work together that couldn’t otherwise because of incompatible interfaces. If we don’t focus on interfaces or classes, we can rephrase the definition to a shorter one: These are patterns that provide a way to allow incompatible types to interact.
The singleton pattern ensures a class has only one instance and provides a global point of access to it.
The command pattern is used to allow an object to store the information needed to execute some other functionality at a later time. For example it can help implement a redo-undo scenario.
The composite pattern describes a group of objects that are to be treated in the same way as a single instance of an object. The intent of a composite is to compose objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly. The visitor pattern separates the algorithm implementation from the data structure. These two patterns can work together. The composite pattern forms a tree structure, and the visitor pattern applies a function to the tree structure and brings the result back.
The template pattern is, as its name suggests, a program or algorithm skeleton.
The private data class pattern is used to encapsulate fields and methods that can be used to manipulate the class instance.
The builder pattern provides abstract steps of building objects. Using this pattern allows a developer to pass different implementations of abstract steps.
The façade pattern allows you to create a higher level interface that can be used to make it easier to invoke underlying class libraries.
The memento pattern saves an object’s internal state for later use.
The chain of responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains a set of logic that describes the types of command objects it can handle and how to pass off those it cannot handle to the next processing object in the chain. The sample in Example 3-2 shows a physical check process that needs to make sure that a person’s age is between 18 and 65, that their weight is no more than 200 kilograms, and that they are taller than 120 centimeters.
The type in Example 3-2 is called a Record. Example 3-2 uses a Record to store a patient’s medical data. It has several named fields that are used to hold the patient’s data. It is very much like a database record. Example 3-1 shows how to define a Record type and create a record object. The sample code creates a point record that has its X and Y fields set to (1, 1).
// define a point record type Point2D = { X : float Y : float } // create original point record let originalPoint = { X = 0.0; Y = 0.0 } // create (1,1) point record let onePoint = { X = 1.0; Y = 1.0 }
The record object implicitly forces data initialization; therefore, initial values are not optional when creating a Record type. The invoker must define the patient with some data, and this eliminates any possible initialization problems.
// define a record to hold a person's age and weight type Record = { Name : string; Age : int Weight: float Height: float } // Chain of responsibility pattern let chainOfResponsibility() = // function to check that the age is between 18 and 65 let validAge record = record.Age < 65 && record.Age > 18 // function to check that the weight is less than 200 let validWeight record = record.Weight < 200. // function to check that the height is greater than 120 let validHeight record = record.Height > 120. // function to perform the check according to parameter f let check f (record, result) = if not result then record, false else record, f(record) // create chain function let chainOfResponsibility = check validAge >> check validWeight >> check validHeight // define two patients' records let john = { Name = "John"; Age = 80; Weight = 180.; Height = 180. } let dan = { Name = "Dan"; Age = 20; Weight = 160.; Height = 190. } printfn "John's result = %b" (chainOfResponsibility (john, true) |> snd) printfn "Dan's result = %b" (chainOfResponsibility (dan, true) |> snd)
Execution result from the chain of responsibility sample
John's result = false Dan's result = true
In the implementation in Example 3-2, three functions (responsibilities) are composed into a chain and the data is passed along the chain when it is being processed. The parameter passed in contains a Boolean variable that decides whether the data can be processed. In Example 3-2, all the functions are in effect AND-ed together. The parameter passed into the first function contains a Boolean value. The successive function can be invoked only if the Boolean value is true.
The other implementation is used for pipelining, as shown in Example 3-3, rather than function composition. The chainTemplate higher-order function takes a process and canContinue function. The canContinue function always returns true, and the process function is a simple “increase one” function. The execution result is 2.
// chain template function let chainTemplate processFunction canContinue s = if canContinue s then processFunction s else s let canContinueF _ = true let processF x = x + 1 //combine two functions to get a chainFunction let chainFunction = chainTemplate processF canContinueF // use pipeline to form a chain let s = 1 |> chainFunction |> chainFunction printfn "%A" s
The other chain of responsibility implementation uses the partial pattern feature in F#. I introduced the unit of measure to make the code readable. The process goes from the first case and stops when the condition is met. The sample code is listed in Example 3-2. The sample code checks the height and weight value for some predefined criteria. The person’s data is checked against NotPassHeight and then NotPassWeight if his height passes the validation criteria. The code also demonstrates how to use the F# unit-of-measure feature, which avoids possible confusion because of the unit of measure used. The parameter for makeCheck is #Person, which means that any object of type Person or derived from a Person type can be passed in.
Example 3-4 uses units-of-measure language constructs within a calculation. Only the number with the same unit of measure can be involved in the same calculation. Example 3-4 shows how to define a kilogram (kg) unit and decorate it with a number.
// define unit-of-measure kg [<Measure>] type kg // define 1kg and 2kg variables let oneKilo = 1<kg> let twoKilo = 1<kg> + 1<kg>
The None and Some(person) syntax in the sample code in Example 3-6 represents a Nullable-type-like data structure called an option. You can think of None as NULL. The special function let (| NotPassHeight | _ |)
is called an active pattern. It takes a person parameter and decides whether the person meets certain criteria. If the person meets the criteria, the function returns Some(person) and triggers the match statement. Example 3-5 shows how to use the Some()/None syntax to check for an odd number. This sample introduced several new concepts. I will come back to these concepts in detail in Chapter 5.
// define an active pattern function to check for an odd number let (| Odd | _ |) x = if x % 2 = 0 then None else Some(x) // define a function to check for an odd number let findOdd x = match x with | Odd x -> printfn "x is odd number" | _ -> printfn "x is not odd number" // check odd number findOdd 3 findOdd 4
x is odd number x is not odd number
// define two units of measure: cm and kg [<Measure>] type cm [<Measure>] type kg // define a person class with its height and weight set to 0cm and 0kg type Person() = member val Height = 0.<cm> with get, set member val Weight = 0.<kg> with get, set // define a higher order function that takes a person record as a parameter let makeCheck passingCriterion (person: #Person) = if passingCriterion person then None //if passing, say nothing, just let it pass else Some(person) //if not passing, return Some(person) // define NotPassHeight when the height does not meet 170cm let (| NotPassHeight | _ |) person = makeCheck (fun p -> p.Height > 170.<cm>) person // define the NotPassWeight when weight does not fall into 100kg and 50kg range let (| NotPassWeight | _ |) person = makeCheck (fun p -> p.Weight < 100.<kg> && p.Weight > 50.<kg>) person // check incoming variable x let check x = match x with | NotPassHeight x -> printfn "this person is not tall enough" | NotPassWeight x -> printfn "this person is out of weight range" | _ -> printfn "good, this person passes" // create a person with 180cm and 75kg let p = Person(Height = 180.<cm>, Weight = 75.<kg>) // perform the chain check check p
Execution result
good, this person passes
The adapter pattern is a design pattern that translates one interface for a type into an interface that is compatible with some other type. An adapter allows classes to work together that normally could not because of incompatible types. In Example 3-8, we use the Generic Invoke(GI) function as an adapter or bridge to invoke two methods of incompatible types. By using the GI function, a common interface is no longer needed and the function can still be invoked. The GI function is a static type constraint function, it requires that type T define a certain member function. For example, in Example 3-7, it requires that the type T has a canConnect function that takes void (unit) and returns a Boolean. (Note that F# requires you to declare a function as “inline” when arguments of the function are statically resolved type parameters such as those in the following code listing.)
// define a GI function let inline canConnect (x : ^T) = (^T : (member CanConnect : unit->bool) x)
The interesting thing about the design pattern implementation in Example 3-8 is that Cat and Dog do not have any common base class or interface. However, they can still be processed in a unified function. This implementation can be used to invoke the legacy code, which does not share any common interface or base class. (You should note, by the way, that this is a sloppy way of solving the problem and should be considered only when no other option is available.)
Imagine that you have two legacy systems that need to be integrated and that you do not have access to the source code. It would be difficult to integrate the systems in other languages, but it’s possible and even easy in F# using the generic invoke technique.
//define a cat class type Cat() = member this.Walk() = printfn "cat walks" // define a dog class type Dog() = member this.Walk() = printfn "dog walks" // adapter pattern let adapterExample() = let cat = Cat() let dog = Dog() // define the GI function to invoke the Walk function let inline walk (x : ^T) = (^T : (member Walk : unit->unit) x) // invoke GI and both Cat and Dog walk(cat) walk(dog)
Execution result from adapter pattern sample
cat walks dog walks
The implementation in Example 3-8 can also be viewed as a bridge pattern.
The command pattern is a design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. Example 3-10 shows how to use the command pattern to implement a redo-undo framework. This is an example of typical usage of the command pattern in the OOP world.
Example 3-9 defines a result using the ref keyword. The ref keyword defines a reference type that points to the value 7. The result is a reference cell. You can think of the ref keyword as a way to define a mutable variable.
F# provides incr and decr to increase or decrease reference cell values by 1. When using the incr function, the increaseA function becomes let increaseA() = incr a
.
The := operator is used to assign a new value to the content of the reference cell. The ! (pronounced bang) operator is used to retrieve the reference cell content.
// define a command record type Command = { Redo: unit->unit; Undo: unit->unit } let commandPatternSample() = // define a mutable storage let result = ref 7 // define the add command let add n = { Redo = (fun _ -> result := !result + n) Undo = (fun _ -> result := !result - n) } // define the minus command let minus n = { Redo = (fun _ -> result := !result - n) Undo = (fun _ -> result := !result + n) } // define an add 3 command let cmd = add 3 printfn "current state = %d" !result // perform add 3 redo operation cmd.Redo() printfn "after redo: %d" !result // perform an undo operation cmd.Undo() printfn "after undo: %d" !result
Execution result from the command pattern sample obtained by invoking the commandPatternSample function
current state = 7 after redo: 10 after undo: 7
There is no storage structure for command history; however, adding such a storage structure is trivial.
According to the MSDN documentation (http://msdn.microsoft.com/en-us/library/dd233186.aspx), a mutable variable should be used instead of a reference cell whenever possible. The preceding code uses a reference cell just for demo purposes. You can convert this code to use a mutable variable.
There is another implementation that emphasizes that the command can be treated like data. The code defines two types of commands: deposit and withdraw. The Do and Undo functions are used to perform the do and undo actions. See Example 3-12.
To implement this Do and Undo functionality, it is helpful to understand the F# discriminated union (DU) feature. Example 3-11 demonstrates how to use a DU to check whether or not the given time is a working hour. Note how the first DU, DayOfAWeek, looks a lot like an enum, but without the default numeric value. In the second example, TWorkingHour, the DU case Hour has a tuple value, where the first element of the tuple is a DayOfAWeek and the second element is an integer.
// define day of the week type DayOfAWeek = | Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday // define working hour type TWorkingHour = | Hour of DayOfAWeek * int // check that the working hour is Monday to Friday 9:00 to 17:00 let isWorkingHour day = match day with | Hour(Sunday, _) -> false | Hour(Saturday, _) -> false | Hour(_, time) -> time >= 9 && time <= 17 // check if Sunday is working hour let sunday = Hour(Sunday, 9) printfn "%A is working hour? %A" sunday (isWorkingHour sunday) // check if Monday 10:00 is working hour let monday = Hour(Monday, 10) printfn "%A is working hour? %A" monday (isWorkingHour monday)
Execution result
Hour (Sunday,9) is working hour? false Hour (Monday,10) is working hour? true
Now that you understand discriminated unions, you can apply them to the command pattern.
// define two command types type CommandType = | Deposit | Withdraw // define the command format, which has a command type and an integer type TCommand = | Command of CommandType * int // mutable variable result let result = ref 7 // define a deposit function let deposit x = result := !result + x // define a withdraw function let withdraw x = result := !result - x // do function to perform a do action based on command type let Do = fun cmd -> match cmd with | Command(CommandType.Deposit, n) -> deposit n | Command(CommandType.Withdraw,n) -> withdraw n // undo function to perform an undo action based on command type let Undo = fun cmd -> match cmd with | Command(CommandType.Deposit, n) -> withdraw n | Command(CommandType.Withdraw,n) -> deposit n // print the current balance printfn "current balance %d" !result // deposit 3 into the account and print the balance let depositCmd = Command(Deposit, 3) Do depositCmd printfn "after deposit: %d" !result // undo the deposit command and print the balance Undo depositCmd printfn "after undo: %d" !result
current balance 7 after deposit: 10 after undo: 7
The observer pattern is a pattern in which a subject object maintains a list of its observer dependents. The subject automatically notifies its dependents of any changes by calling one of the dependent’s methods. The implementation in Example 3-13 passes the function into the subject, and the subject notifies its changes by calling this function along with some parameters.
// define a subject type Subject() = // define a default notify function let mutable notify = fun _ -> () // subscribe to a notification function member this.Subscribe notifyFunction = let wrap f i = f i; i notify <- wrap notifyFunction >> notify // reset notification function member this.Reset() = notify <- fun _ -> () // notify when something happens member this.SomethingHappen k = notify k // define observer A type ObserverA() = member this.NotifyMe i = printfn "notified A %A" i // define observer B type ObserverB() = member this.NotifyMeB i = printfn "notified B %A" i // observer pattern let observer() = // create two observers let a = ObserverA() let b = ObserverB() // create a subject let subject = Subject() // let observer subscribe to subject subject.Subscribe a.NotifyMe subject.Subscribe b.NotifyMeB // something happens to the subject subject.SomethingHappen "good"
Execution result from the observer pattern sample obtained by invoking the observer function
notified B "good" notified A "good"
F#’s Observable module can be used to implement this pattern as well. In Example 3-14, an event is defined along with three observers of the event. Compared to the version in Example 3-13, this version is much more lightweight. The myEvent value is bound to an instance of the F# event type. For the Observable module to subscribe to the event, you have to publish the event. After the event is published, the Observable.add function is used to add the event-handler function to this event. When the event is fired by using Trigger, all the event-handler functions will be notified.
// define an event let myEvent = Event<_>() // define three observers let observerA = fun i -> printfn "observer A noticed something, its value is %A" i let observerB = fun i -> printfn "observer B noticed something, its value is %A" i let observerC = fun i -> printfn "observer C noticed something, its value is %A" i // publish the event and add observerA myEvent.Publish |> Observable.add observerA // publish the event and add observerA myEvent.Publish |> Observable.add observerB // publish the event and add observerA myEvent.Publish |> Observable.add observerC //fire event with value 1 myEvent.Trigger 1
observer A noticed something, its value is 1 observer B noticed something, its value is 1 observer C noticed something, its value is 1
The decorator pattern can be used to extend (a.k.a. decorate) the functionality of an object at run-time. In Example 3-15, the decorator pattern is used along with the composite operator to add new logic to the existing function. As the function is passed dynamically into a structure, the run-time behavior can be easily changed. The sample code defines a property that exposes a function. This function can then be changed at runtime.
// define the Divide class type Divide() = // define basic divide function let mutable divide = fun (a,b) -> a / b // define a property to expose the function member this.Function with get() = divide and set(v) = divide <- v // method to invoke the function member this.Invoke(a,b) = divide (a,b) // decorator pattern let decorate() = // create a divide instance let d = Divide() // set the check zero function let checkZero (a,b) = if b = 0 then failwith "a/b and b is 0" else (a,b) // invoke the function without check zero try d.Invoke(1, 0) |> ignore with e -> printfn "without check, the error is = %s" e.Message // add the check zero function and then invoke the divide instance d.Function <- checkZero >> d.Function try d.Invoke(1, 0) |> ignore with e -> printfn "after add check, error is = %s" e.Message
Execution result from the decorator pattern sample obtained by invoking the decorate function
without check, the error is = Attempted to divide by zero. after add check, error is = a/b and b is 0
The proxy pattern uses a class that acts as a placeholder or interface for another object or function. It’s often used for caching, to control access, or to delay the execution or creation of an object that is costly in the form of time or resources. See Example 3-16. The CoreComputation class hosts two calculation functions, named Add and Sub. The class also exposes a proxy class from which a user can get access to the computation.
// define core computation type CoreComputation() = member this.Add(x) = x + 1 member this.Sub(x) = x - 1 member this.GetProxy name = match name with | "Add" -> this.Add, "add" | "Sub" -> this.Sub, "sub" | _ -> failwith "not supported" // proxy implementation let proxy() = let core = CoreComputation() // get the proxy for the add function let proxy = core.GetProxy "Add" // get the compute from proxy let coreFunction = fst proxy // get the core function name let coreFunctionName = snd proxy // perform the core function calculation printfn "performed calculation %s and get result = %A" coreFunctionName (coreFunction 1)
Execution result from the proxy pattern sample obtained by invoking the proxy function
performed calculation add and get result = 2
The strategy pattern is a software design pattern whereby algorithms can be selected and used at runtime. Example 3-17 uses a function to hold different strategies. During runtime, the strategy can be modified.
// quick sort algorithm let quicksort l = printfn "quick sort" // shell short algorithm let shellsort l = printfn "shell short" // bubble short algorithm let bubblesort l = printfn "bubble sort" // define the strategy class type Strategy() = let mutable sortFunction = fun _ -> () member this.SetStrategy f = sortFunction <- f member this.Execute n = sortFunction n let strategy() = let s = Strategy() // set strategy to be quick sort s.SetStrategy quicksort s.Execute [1..6] // set strategy to be bubble sort s.SetStrategy bubblesort s.Execute [1..6]
Execution result from the strategy pattern sample obtained by invoking the strategy function
quick sort bubble sort
The sample code does not really implement three sorting algorithms. Instead, the code simply outputs the name of the algorithm that would be used.
Example 3-17 shows how to implement this pattern using the OOP paradigm. However, the strategy pattern can be implemented more succinctly with a functional approach. Example 3-18 shows how to use the higher-order function named executeStrategy to implement this pattern using a functional paradigm.
// quick sort algorithm let quicksort l = printfn "quick sort" // shell short algorithm let shellsort l = printfn "shell short" // bubble short algorithm let bubblesort l = printfn "bubble sort" let executeStrategy f n = f n let strategy() = // set strategy to be quick sort let s = executeStrategy quicksort // execute the strategy against a list of integers [1..6] |> s // set strategy to be bubble sort let s2 = executeStrategy bubblesort // execute the strategy against a list of integers [1..6] |> s2
The state pattern is used to represent the ability to vary the behavior of a routine depending on the state of an object. This is a clean way for an object to partially change its type at runtime. Example 3-19 shows that the interest rate is decided by the internal state: account balance. The higher the balance is, the higher the interest is that a customer will receive. In the sample, I also demonstrate how to use the unit-of-measure feature.
// define account state type AccountState = | Overdrawn | Silver | Gold // define unit of measure as US dollar [<Measure>] type USD // define an account that takes the unit of measure type Account<[<Measure>] 'u>() = // field to hold the account balance let mutable balance = 0.0<_> // property for account state member this.State with get() = match balance with | _ when balance <= 0.0<_> -> Overdrawn | _ when balance > 0.0<_> && balance < 10000.0<_> -> Silver | _ -> Gold // method to pay the interest member this.PayInterest() = let interest = match this.State with | Overdrawn -> 0. | Silver -> 0.01 | Gold -> 0.02 interest * balance // deposit into the account member this.Deposit x = let a = x balance <- balance + a // withdraw from account member this.Withdraw x = balance <- balance - x // implement the state pattern let state() = let account = Account() // deposit 10000 USD account.Deposit 10000.<USD> // pay interest according to current balance printfn "account state = %A, interest = %A" account.State (account.PayInterest()) // deposit another 2000 USD account.Withdraw 2000.<USD> // pay interest according to current balance printfn "account state = %A, interest = %A" account.State (account.PayInterest())
Execution result from the state pattern sample obtained by invoking the state function
account state = Gold, interest = 200.0 account state = Silver, interest = 80.0
In F#, one way to implement a state machine is with a MailboxProcessor. The F# MailboxProcessor can be viewed as a message queue. It takes an asynchronous workflow as the processing logic. The asynchronous workflow will be introduced in the next chapter, and it can be thought of as a simple function being executed on a background thread. The Post method is used to insert a message into the queue, and the Receive method is used to get the message out of the queue. In Example 3-20, the variable inbox represents the message queue. When the state machine starts, it goes to state0, which is represented by the state0() function, and waits for user input. The state machine will transition to another state according to the user’s input.
open Microsoft.FSharp.Control type States = | State1 | State2 | State3 type StateMachine() = let stateMachine = new MailboxProcessor<States>(fun inbox -> let rec state1 () = async { printfn "current state is State1" // <your operations> //get another message and perform state transition let! msg = inbox.Receive() match msg with | State1 -> return! (state1()) | State2 -> return! (state2()) | State3 -> return! (state3()) } and state2() = async { printfn "current state is state2" // <your operations> //get another message and perform state transition let! msg = inbox.Receive() match msg with | State1 -> return! (state1()) | State2 -> return! (state2()) | State3 -> return! (state3()) } and state3() = async { printfn "current state is state3" // <your operations> //get another message and perform state transition let! msg = inbox.Receive() match msg with | State1 -> return! (state1()) | State2 -> return! (state2()) | State3 -> return! (state3()) } and state0 () = async { //get initial message and perform state transition let! msg = inbox.Receive() match msg with | State1 -> return! (state1()) | State2 -> return! (state2()) | State3 -> return! (state3()) } state0 ()) //start the state machine and set it to state0 do stateMachine.Start() member this.ChangeState(state) = stateMachine.Post(state) let stateMachine = StateMachine() stateMachine.ChangeState(States.State2) stateMachine.ChangeState(States.State1)
current state is state2 current state is State1
The factory pattern in Example 3-21 is an object-oriented design pattern used to implement the concept of factories. It uses the function keyword as shortcut to the match statement. It can create an object without specifying the exact class of object that will be created. Example 3-22 shows an example that uses the object expression to implement the factory pattern.
// define two types type Type = | TypeA | TypeB // check with function keyword let checkWithFunction = function | TypeA -> printfn "type A" | TypeB -> printfn "type B" // check with match keyword let checkWithMatch x = match x with | TypeA -> printfn "type A" | TypeB -> printfn "type B"
In Example 3-22, the factory inside factoryPattern is actually a function. It is a shortcut for a match statement. The checkWithFunction and checkWithMatch functions in Example 3-21 are equivalent.
// define the interface type IA = abstract Action : unit -> unit // define two types type Type = | TypeA | TypeB let factoryPattern() = // factory pattern to create the object according to the input object type let factory = function | TypeA -> { new IA with member this.Action() = printfn "I am type A" } | TypeB -> { new IA with member this.Action() = printfn "I am type B" } // create type A object let obj1 = factory TypeA obj1.Action() // create type B object let obj2 = factory TypeB obj2.Action()
Execution result from the factory pattern sample obtained by invoking the factoryPattern function
I am type A I am type B
The factory function returns an object that is not familiar. Actually, the return type is something called an object expression, and this lightweight syntax can simplify your code significantly. If the object is not involved in inheritance, you can pretty much use an object expression to replace a class definition completely. Example 3-23 shows how to create an instance of interface IA using object expression syntax.
The singleton pattern is a design pattern used to implement the mathematical concept of a singleton. It restricts the instantiation of a class to a single instance. This is useful when exactly one object is needed to coordinate actions across the system. One example of a singleton in F# is a value. An F# value is immutable by default, and this guarantees there is only one instance. Example 3-24 shows how to make sure that an F# class instance is a singleton. The sample declares a private constructor and ensures that the class has only one instance in memory.
The composite pattern is a partitioning design pattern. The composite pattern describes a group of objects that are to be treated in the same way as a single instance of that object. The typical application is a tree structure representation. Example 3-25 demonstrates a tree structure. The sample focuses more on how to access this tree structure and bring back the result.
The dynamically generated wrapper object can be treated like a visitor to the tree. The visitor accesses the node and brings the result back to the invoker. In the sample code, the CompositeNode structure not only defines the tree but also defines three common ways to traverse the tree. It does the heavy lifting by encapsulating the tree traversal algorithm. The visitor defines how to process the single node and is responsible for bringing the result back to the invoker. In this sample, the visitor adds the value in the tree nodes and brings back the sum.
// define visitor interface type IVisitor<'T> = abstract member Do : 'T -> unit // define a composite node type CompositeNode<'T> = | Node of 'T | Tree of 'T * CompositeNode<'T> * CompositeNode<'T> with // define in-order traverse member this.InOrder f = match this with | Tree(n, left, right) -> left.InOrder f f n right.InOrder(f) | Node(n) -> f n // define pre-order traverse member this.PreOrder f = match this with | Tree(n, left, right) -> f n left.PreOrder f right.PreOrder f | Node(n) -> f n // define post order traverse member this.PostOrder f = match this with | Tree(n, left, right) -> left.PostOrder f right.PostOrder f f n | Node(n) -> f n let invoke() = // define a tree structure let tree = Tree(1, Tree(11, Node(12), Node(13)), Node(2)) // define a visitor, it gets the summary of the node values let wrapper = let result = ref 0 ({ new IVisitor<int> with member this.Do n = result := !result + n }, result) // pre-order iterates the tree and prints out the result tree.PreOrder (fst wrapper).Do printfn "result = %d" !(snd wrapper)
Execution result from the composite pattern sample obtained by calling the invoke function
result = 39
The template pattern is, as its name suggests, a program or algorithm skeleton. It is a behavior-based pattern. In F#, we have higher-order functions that can serve as a template to generate other functions. It is natural to use higher-order functions to implement this pattern. Example 3-26 defines a three-stage database operation function named TemplateF. The actual implementation is provided outside of this skeleton function. I do not assume the database connection and query are all the same, so three functions are left outside of the class definition, and the user can define and pass in their own version of each.
// the template pattern takes three functions and forms a skeleton function named TemplateF type Template(connF, queryF, disconnF) = member this.Execute(conStr, queryStr) = this.TemplateF conStr queryStr member this.TemplateF = let f conStr queryStr = connF conStr queryF queryStr disconnF () f // connect to the database let connect conStr = printfn "connect to database: %s" conStr // query the database with the SQL query string let query queryStr = printfn "query database %s" queryStr // disconnect from the database let disconnect () = printfn "disconnect" let template() = let s = Template(connect, query, disconnect) s.Execute("<connection string>", "select * from tableA") template()
Execution result from the template pattern sample obtained by invoking the template function
connect to database: <connection string> query database select * from tableA disconnect
The connect, query, and disconnect functions can be implemented as private functions in a class.
The class definition is convenient for C# projects that need to reference the implementation of this design pattern in an F# project. However, the class is not necessary in an F#-only solution. Example 3-27 shows how to use higher-order functions to implement the template pattern.
// connection, query, and disconnect functions let connect(conStr ) = printfn "connect using %s" conStr let query(queryStr) = printfn "query with %s" queryStr let disconnect() = printfn "disconnect" // template pattern let template(connect, query, disconnect) (conStr:string) (queryStr:string)= connect(conStr) query(queryStr) disconnect() // concrete query let queryFunction = template(connect, query, disconnect) // execute the query do queryFunction "<connection string>" "select * from tableA"
The private data class pattern is a design pattern that encapsulates class properties and associated data manipulation. The purpose of the private accessibility is to prevent the modification of these values. C# uses the readonly property, which does not have a setter function, to solve this problem. F# values are immutable by default, so implementing this readonly type of behavior is supported inherently. In the following example, I use an F# record type to implement the pattern by extending the record type. The with keyword in the code shown in Example 3-28 is a way to tell the compiler that some property, method, or both will be added to the record type. In the sample code, the circle data remains the same once it is created. Some object-oriented implementations even implement another class so that there is little chance to modify the values. The immutability of record types eliminates the needs of a second class, as well as the need for explicitly defining getter-only properties with a keyword.
type Circle = { Radius : float; X : float; Y : float } with member this.Area = this.Radius**2. * System.Math.PI member this.Diameter = this.Radius * 2. let myCircle = {Radius = 10.0; X = 5.0; Y = 4.5} printfn "Area: %f Diameter: %f" myCircle.Area myCircle.Diameter
The builder pattern provides abstract steps of building objects. This allows you to pass different implementations of specific abstract steps. Example 3-29 demonstrates the abstract steps of making a pizza. The invoker can pass in different implementation steps to the cook function to generate different pizzas.
// pizza interface type IPizza = abstract Name : string with get abstract MakeDough : unit->unit abstract MakeSauce : unit->unit abstract MakeTopping: unit->unit // pizza module that defines all recipes [<AutoOpen>] module PizzaModule = let makeNormalDough() = printfn "make normal dough" let makePanBakedDough() = printfn "make pan baked dough" let makeCrossDough() = printfn "make cross dough" let makeHotSauce() = printfn "make hot sauce" let makeMildSauce() = printfn "make mild sauce" let makeLightSauce() = printfn "make light sauce" let makePepperoniTopping() = printfn "make pepperoni topping" let makeFiveCheeseTopping() = printfn "make five cheese topping" let makeBaconHamTopping() = printfn "make bacon ham topping" // define a pepperoni pizza recipe let pepperoniPizza = { new IPizza with member this.Name = "Pepperoni Pizza" member this.MakeDough() = makeNormalDough() member this.MakeSauce() = makeHotSauce() member this.MakeTopping() = makePepperoniTopping() } // cook takes pizza recipe and makes the pizza let cook(pizza:IPizza) = printfn "making pizza %s" pizza.Name pizza.MakeDough() pizza.MakeSauce() pizza.MakeTopping() // cook pepperoni pizza cook pepperoniPizza
Execution result from the builder pattern sample
making pizza Pepperoni Pizza make normal dough make hot sauce make pepperoni topping
The pizza interface and object expression give the program a good structure, but it makes things unnecessarily complicated. The builder pattern requires the actual processing function or functions be passed in, which is a perfect use of higher-order functions. Example 3-30 uses a higher-order function to eliminate the interface and object expression.
// pizza module that defines all recipes [<AutoOpen>] module PizzaModule = let makeNormalDough () = printfn "make normal dough" let makePanBakedDough () = printfn "make pan baked dough" let makeCrossDough() = printfn "make cross dough" let makeHotSauce() = printfn "make hot sauce" let makeMildSauce() = printfn "make mild sauce" let makeLightSauce() = printfn "make light sauce" let makePepperoniTopping() = printfn "make pepperoni topping" let makeFiveCheeseTopping() = printfn "make five cheese topping" let makeBaconHamTopping() = printfn "make bacon ham topping" // cook takes the recipe and ingredients and makes the pizza let cook pizza recipeSteps = printfn "making pizza %s" pizza recipeSteps |> List.iter(fun f -> f()) [ makeNormalDough; makeMildSauce makePepperoniTopping ] |> cook "pepperoni pizza"
The façade pattern provides a higher-level interface that makes invoking an underlying class library easier, more readable, or both. Example 3-31 shows how to perform an employment background check.
// define Applicant record type Applicant = { Name : string } // library to perform various checks [<AutoOpen>] module SubOperationModule = let checkCriminalRecord (applicant) = printfn "checking %s criminal record..." applicant.Name true let checkPastEmployment (applicant) = printfn "checking %s past employment..." applicant.Name true let securityClearance (applicant, securityLevel) = printfn "security clearance for %s ..." applicant.Name true // façade function to perform the background check let isBackgroundCheckPassed(applicant, securityLevel) = checkCriminalRecord applicant && checkPastEmployment applicant && securityClearance(applicant, securityLevel) // create an applicant let jenny = { Name = "Jenny" } // print out background check result if isBackgroundCheckPassed(jenny, 2) then printfn "%s passed background check" jenny.Name else printfn "%s failed background check" jenny.Name
Execution result from the façade pattern sample
checking Jenny criminal record... checking Jenny past employment... security clearance for Jenny ... Jenny passed background check
The memento pattern saves an object’s internal state so that it can be used later. In Example 3-32, the particle class saves its location information and later restores that information back to the saved location. If the state data is relatively small, a list storage can easily turn the memento pattern into a redo-undo framework.
// define location record type Location = { X : float; Y : float } // define a particle class with a location property type Particle() = let mutable loc = {X = 0.; Y = 0.} member this.Loc with get() = loc and private set v = loc <- v member this.GetMemento() = this.Loc member this.Restore v = this.Loc <- v member this.MoveXY(newX, newY) = loc <- { X = newX; Y = newY } // create a particle let particle = Particle() // save current state let currentState = particle.GetMemento() printfn "current location is %A" particle.Loc // move particle to new location particle.MoveXY(2., 3.) printfn "current location is %A" particle.Loc // restore particle to previous saved location particle.Restore currentState printfn "current location is %A" particle.Loc
As I mentioned in the beginning of this chapter, design patterns have been criticized since their birth. Many functional programmers believe that design patterns are not needed when programming in a functional style. Peter Norvig, in his paper “Design Patterns in Dynamic Languages,” claims that design patterns are just missing language features and demonstrates that design patterns can be simplified or eliminated completely when using a different language. I am not planning to be part of these discussions. Design patterns are a way to represent a system or idea. It is really a de facto and concise way for many computer professionals to describe system design. If the program is simple and small, design patterns are often unnecessary. For these scenarios, the use of basic data and flow-control structure is enough. However, when a program becomes large and complicated, a tested approach is needed to organize thinking and avoid possible design flaws or bugs. If the basic data structure is analogous to a word in a sentence, design patterns can be viewed as the idea to organize an article.
As a functional-first programming language, F# is adept at creating code with a functional style. For example, the pipeline and function composition operators make function operation much easier. Instead of being confined to a class, the function can be freely passed and processed like data in F#. If the design pattern is mainly about how to pass an action/operation or coordinate the flow of an operation, the pipeline and function composition operators can definitely simplify the implementation. The chain of responsibility pattern is an example. The biggest change from C# is that a function in F# is no longer auxiliary to the data; instead, it can be encapsulated, stored, and manipulated in a class. The data (field and property) in a class can actually be provided as a function or as method parameters and remain auxiliary to the function. Additionally, the presence of a class is optional if the class only serves as an operation container. The builder pattern demonstrates a way to eliminate the class while still implementing the same functionality.
Functional programming can still have a structure to encapsulate logic into a unit. Functions, which can be treated like data, can be encapsulated in a class or inside a closure and, more importantly, the application of object expressions provides an even simpler way to organize the code. Example 3-33 shows different ways to encapsulate the data.
Object expressions are great, because the type is created on the fly by the compiler. Instead of inventing a permanent boilerplate class to hold the function and data, you can use object expressions to quickly organize functions and data into a unit and get the job done. Imagine an investment bank with a bunch of mathematicians who lack a computer background: object expressions can let them quickly transform their knowledge into code without worrying about programmers complaining about their inability to implement complex inheritance hierarchies. The flattened structure from the object expression is a straightforward and suitable approach for quick prototyping and agile development. The command pattern is a good sample for demonstrating how to use object expressions to simplify the design.
Both functional programming and object-oriented programming have their own way of reusing the code. Object-oriented programming uses inheritance, while functional programming uses higher-order functions. Both approaches have loyal followers, and you might already be convinced that one is superior to the other. I say that both approaches have their own advantages under certain circumstances. Unfortunately, neither is a silver bullet that can be used to solve all problems. Using the right tool for the right job is the key. F#, which supports both OO and functional programming, provides both approaches, and this gives the developer the liberty to use the best way to perform the system design.
F# provides the alternative to encapsulation (object expressions) and inheritance (higher order functions): polymorphism. It can also be implemented by higher-order functions when given different parameters. This is yet another example of how F# provides a wide set of tools for developers to implement their components and systems.
In addition, the adapter pattern introduces the GI function, which breaks class encapsulation and makes possible communication between objects that do not share a common base class. It is not a recommended way to use the original object-oriented design; however, it is a feasible approach to wrap legacy code because of inaccessibility to the source code. It is not fair to blame a gun for causing crime and not blame the criminal. Likewise, F# provides this approach, but I’ll leave the decision to you regarding when and how to use it.
It is totally fine to copy a standard object-oriented approach when doing system design, especially when someone is new to a language. If you are motivated to use F# to write design patterns, here are some principles that I used to implement the design patterns in this chapter. If the design pattern is a behavior design pattern, its main focus is on how to organize the function, so consider using the function composition and pipeline operators. If the function needs to be organized into a unit, put the function into a module and use object expressions to organize the function. If the design pattern is a structural design pattern, I always question why extra structure is needed. If the extra structure is a placeholder for functionality, higher-order functions most likely will do the same job. If the extra structure is needed to make two unrelated objects work together, the GI function could be a good candidate to simplify the design.
F# is a young language and how to properly apply its language feature into the system design is still a new topic. Keep in mind that F# provides the OOP way of implementing class encapsulation, inheritance, and polymorphism. This chapter is only a small step to explore how to use F# in system design.