Strategy in F #

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 work using the provided strategy object to populate implementation parts. Note: in general, strategy objects do not contain state; they simply provide polymorphic implementation of individual steps.

The strategy class is as follows:

 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 # with lambdas (and generally get rid of the strategy object), but the good thing about this implementation is that expandable classes have their own Step1 and Step2 together.

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

Thoughts:

I could introduce individual step functions into the Work function, similar to the idea of ​​observation.

I could also create a type that collects two functions and passes the value of that type with:

 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 I am trying to achieve: it keeps the implementation steps close together so that they can be seen as a group. But is this idea (creating a type containing only the values ​​of functions) idiomatic in the functional paradigm? Any other thoughts?

+6
source share
4 answers

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 
+4
source

Here you should use F # object expressions :

 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: inheritance flexibility and easy function-like syntax.

Your approach using function records is good, but not the most idiomatic. Here is what the F # Component Design Guide (page 9) suggests:

In F # there are a number of ways to represent a dictionary of operations, for example, using sets of functions or function records. In general, we recommend using the goal interface types for this.

EDIT:

Writing updates using with is great, but intellisense doesn't work very well when the write fields are functions. Using interfaces, you can additionally configure transfer parameters inside object expressions, for example.

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

or resort to implementing an interface using interface IStrategy with (this will be the same as using C #) when you need more extensibility.

+9
source

You mentioned the ability to just use lambdas in C #. For multi-step strategies, this is often idiomatic. This can be very 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; valid types step1 and step2 enough. In languages ​​without functions of a higher order (which, I believe, is the parameter in which the strategy template was invented), you do not have this option and the need, namely, interfaces.

The function f here does not seem to care if the relations step1 and step2 are related. But if the caller does, nothing prevents him from combining them into a data structure. For example, using @pad's answer,

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

In general, the "idiomatic path" depends on why you consider the strategic pattern in the first place. A strategy template is stitching functionality; higher order functions are often very good for this.

+6
source

Object expressions support only one interface at a time. If 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 returns 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" 
+1
source

All Articles