Since functions are manipulatable and composable in F#, the strategy pattern is ubiquitous.
For example, suppose I have a type definition for a point and path elements:
type Point = { X:float; Y:float }
type PathOp =
| Close
| MoveTo of Point
| LineTo of Point
| CurveTo of Point * Point * Point
and function types:
type PointMutator = Point->Point
type PathOpMutator = PathOp->PathOp
NB: in this context a mutator is a function that given an object returns a new object of the same type with the contents changed in some way (maybe).
I can write an object to change elements in a sequence of paths like this:
let pathMutator (mutator:PathOpMutator) path =
path |> Seq.map(mutator)
Which creates a new sequence of path operations creating mutations of each one.
Now, I can do this:
let pathPointMutator (mutator:PointMutator) op =
match op with
| Close -> Close
| MoveTo(pt) -> MoveTo(mutator(pt))
| LineTo(pt) -> LineTo(mutator(pt))
| CurveTo(cp1, cp2, dp) -> CurveTo(mutator(cp1), mutator(cp2), mutator(cp3))
So if I wanted to write code to offset the entire contents of a path, I would visit each element of the path applying an offset strategy:
let pointOffseter offset pt =
{ X = offset.X + pt.X; Y = offset.Y + pt.Y }
let offsetPath offset = pathMutator (pathPointMutator (pointOffsetter offset))
Now, I'm heavily using partial function application here - when I pass only offset into pointOffsetter, I'm getting back a function of Point->Point
which will add the bound offset to its argument. This function is in turn partially applied to pathPointMutator which returns a function of PathOp->PathOp
which is partially applied to pathMutator returning a function of Seq<PathOp>->Seq<PathOp>
.
Essentially, what I've done is created a means of applying a strategy to each path operation in order to create new path operations.
Rather than using an interface to define the strategy, I'm instead using a strongly typed function to define the strategy. The concept is the same but the concrete implementation is different.
If you want to interoperate with other .NET languages you can do this with interfaces if you really want:
type ICalculate =
abstract member Calculate : float->float->float
type Adder =
interface ICalculate with
member this.Calculate x y = x + y
type Subber =
interface ICalculate with
member this.Calculate x y = x - y
type Responder =
member private this.GetCalculator(op) =
match op with
| '+' = new Adder()
| '-' = new Subber()
| _ -> ivalidArg "op" "op not defined"
member this.Respond op x y =
let calc = GetCalculator(op)
calc x y
This is, of course, way too much work just to do calculations, but your focus should be on the blind usage of an ICalculator without knowing it's implementation details.
Now, to be more precise in the strategy definition, Responder
is more accurate in that the actual strategy is selected late, whereas in the PathOp
example, it's done a priori. This is one of the things in the GoF patterns that can be aggravating if you're trying to be precise - many of the patterns overlap and your actual implementation may not have a solid taxonomy.