6

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?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
CSJ
  • 3,641
  • 2
  • 19
  • 29

4 Answers4

9

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.

pad
  • 41,040
  • 7
  • 92
  • 166
  • This looks pretty slick. One comment though, the types behind the values strategyA and strategyB are now anonymous and can't be re-used (i.e. inherited); whereas in the record approach you could make a new strategy out of an existing one by replacing some of its steps using the `with` syntax. Though I do like how the `this`/`x` value is available to be used should it be needed. – CSJ Apr 08 '14 at 14:43
  • Neat, this is like anonymous classes in Java, which I really missed when I started with C# (and don't understand why it was never implemented there; so convenient). – Dax Fohl Apr 08 '14 at 15:16
6

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.

Søren Debois
  • 5,598
  • 26
  • 48
  • I think in this case I would prefer to keep the `step1` and `step2` functions related from `f`'s perspective. I would be more likely to bundle a pair of functions as a tuple than provide each one individually. In turn, I would likely then collect them in a record type (my original thought) or an object expression (@pad's idea). – CSJ Apr 08 '14 at 15:23
  • I think this is where the best solution depends on what `f` is actually doing. In many cases, _especially_ when the strategy's components are functional (no side-effects), they don't really have to be related. But, sure, far from all. – Søren Debois Apr 08 '14 at 15:29
5

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
Wesley Wiser
  • 9,491
  • 4
  • 50
  • 69
  • This looks at the same time extremely promising, and extremely terrifying. On the one hand the syntax is lightweight and easy to follow; on the other hand, the "complete picture" of any given strategy is distributed among the case statements of different functions. Can you comment on this -- is this different grouping of functionality something I'll just have to get used to? – CSJ Apr 09 '14 at 19:04
  • @CSJ That is, in my opinion, one of the defining characteristics of FP vs OOP. I talk about this more here: https://programmers.stackexchange.com/a/209616/4132 – Wesley Wiser Apr 09 '14 at 19:10
  • This answer (and the linked post) are extremely descriptive and helpful. The one point that stands out in the linked post is "OOP encourages bundling of data and behavior while functional programming encourages separating them." If you had included this in your answer in addition to linking your other post, I would have accepted it immediately. :) – CSJ Apr 09 '14 at 19:21
1

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"
gradbot
  • 13,732
  • 5
  • 36
  • 69