Question

In C# I have the following code:

public class SomeKindaWorker
{
    public double Work(Strategy strat)
    {
        int i = 4;
        // some code ...
        var s = strat.Step1(i);
        // some more code ...
        var d = strat.Step2(s);
        // yet more code ...
        return d;
    }
}

This is a piece of code that can do some kind of work by using a provided strategy object to fill in parts of the implementation. Note: in general the strategy objects do not contain state; they merely polymorphically provide implementations of individual steps.

The strategy class looks like this:

public abstract class Strategy
{
    public abstract string Step1(int i);
    public abstract double Step2(string s);
}

public class StrategyA : Strategy
{
    public override string Step1(int i) { return "whatever"; }
    public override double Step2(string s) { return 0.0; }
}

public class StrategyB : Strategy
{
    public override string Step1(int i) { return "something else"; }
    public override double Step2(string s) { return 4.5; }
}

Observation: The same effect can be achieved in C# through the use of lambdas (and getting rid of the strategy object altogether), but the nice thing about this implementation is that the extending classes have their Step1 and Step2 implementations together.

Question: What is an idiomatic implementation of this idea in F#?

Thoughts:

I could inject individual step functions into the Work function, similar to the idea in the observation.

I could also create a type that collects two functions, and pass a value of that type through:

type Strategy = { Step1: int -> string; Step2: string -> double }
let strategyA = { Step1 = (fun i -> "whatever"); Step2 = fun s -> 0.0 }
let strategyB = { Step1 = (fun i -> "something else"); Step2 = fun s -> 4.5 }

This seems like the closest match to what I'm trying to achieve: it keeps the implementing steps close together so that they may be examined as a bunch. But is this idea (creating a type to contain function values only) idiomatic in the functional paradigm? Any other thoughts?

Was it helpful?

Solution 3

Here's a more functional approach to the question:

type Strategy =
    | StrategyA
    | StrategyB

let step1 i = function
    | StrategyA -> "whatever"
    | StrategyB -> "something else"

let step2 s = function
    | StrategyA -> 0.0
    | StrategyB -> 4.5

let work strategy = 
    let i = 4
    let s = step1 i strategy
    let d = step2 s strategy
    d

OTHER TIPS

You should use F# object expressions here:

type IStrategy =
    abstract Step1: int -> string
    abstract Step2: string -> double

let strategyA =
    { new IStrategy with
        member x.Step1 _ = "whatever"
        member x.Step2 _ = 0.0 }

let strategyB =
    { new IStrategy with
        member x.Step1 _ = "something else"
        member x.Step2 _ = 4.5 }

You get the best of both worlds: flexibility of inheritance and lightweight syntax like that of functions.

Your approach using records of functions is fine but isn't the most idiomatic one. Here is what F# Component Design Guidelines (page 9) suggests:

In F# there are a number of ways to represent a dictionary of operations, such as using tuples of functions or records of functions. In general, we recommend you use interface types for this purpose.

EDIT:

Record updates using with are great but intellisense doesn't work really well when record fields are functions. Using interfaces, you can customize further by passing parameters inside object expressions e.g.

let createStrategy label f =
    { new IStrategy with 
        member x.Step1 _ = label
        member x.Step2 s =  f s }

or resort to interface implementation using interface IStrategy with (it would be the same as C# approach) when you need more extensibility.

You mention the possibility of simply using lambdas in C#. For strategies with few steps, this is often idiomatic. It can be really convenient:

let f step1 step2 = 
    let i = 4
    // ...
    let s = step1 i
    // ...
    let d = step2 s
    //  ...
    d

No need for interface definitions or object expressions; the inferred types of step1 and step2 is enough. In languages without higher-order functions (which is the setting in which the Strategy Pattern was invented, I believe), you don't have this option and need instead, e.g., interfaces.

The function f here presumably doesn't care if step1 and step2 are related. But if the caller does, nothing is preventing him from bundling them up in a data structure. E.g., using @pad's answer,

let x = f strategyA.Step1 strategyA.Step2
// val it = 0.0 

In general, the "idiomatic way" depends on why you are considering the Strategy Pattern in the first place. The Strategy Pattern is about stitching together functionality; higher-order functions are often really good for that too.

Object expressions only support one interface at a time. In case you need two, use a type definition.

type IStrategy =
    abstract Step1: int -> string
    abstract Step2: string -> double

type strategyA() =
    let mutable observers = []

    interface System.IObservable<string> with
        member observable.Subscribe(observer)  =
            observers <- observer :: observers
            { new System.IDisposable with
                 member this.Dispose() =
                    observers <- observers |> List.filter ((<>) observer)}

    interface IStrategy with
        member x.Step1 _ = 
            let result = "whatever"
            observers |> List.iter (fun observer -> observer.OnNext(result))
            result
        member x.Step2 _ = 0.0

type SomeKindaWorker() =
    member this.Work(strategy : #IStrategy) =
        let i = 4
        // some code ...
        let s = strategy.Step1(i)
        // some more code ...
        let d = strategy.Step2(s)
        // yet more code ...
        d

let strat = strategyA()
let subscription = printfn "Observed: %A" |> strat.Subscribe
SomeKindaWorker().Work(strat) |> printfn "Result: %A"
subscription.Dispose()

Another pattern that I often see is returning object expressions from functions.

let strategyB(setupData) =
    let b = 3.0 + setupData

    { new IStrategy with
        member x.Step1 _ = "something else"
        member x.Step2 _ = 4.5 + b }

This allows you to initialize your strategy.

SomeKindaWorker().Work(strategyB(2.0)) |> printfn "%A"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top