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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With