I am redesigning a library and I am not happy with the current design pattern. This question concerns the use of the strategy pattern in conjunction with a State monad
I have a Filter
. All it does, in its basic implementation, is to take a some datafeed of type 'd
and update itself, generating a new updated copy of itself.
[<AbstractClass>]
type Filter<'d, 'F> (state: 'F) =
member val StateVariable = state with get
abstract member Update: 'd -> Filter<'d, 'F>
I have then a ISignalGenerator
, that takes a filter, environmental data and process it to generate a Signal
of type 'S
.
type ISignalGenerator<'d, 'F, 'S> =
abstract member GenerateSignal: 'd -> Filter<'d,'F> -> 'S
The SignalGenerator
is a strategy pattern object. On SignalGenerator
's implementations, the library user mounts the functions that will be used and combined to generate the Signal
.
I could wrap my code in a state monad. Together with some environmental variables (the datafeed), the state monad will carry carry along 'Filter' as state. SignalGenerator
will then get state updates via the state monad (the datafeed of type 'd
and the Filter
)
The design issue I have is that I'd like to decouple the SignalGenerator
type from the development of the workflow, i.e. I'd like to avoid nesting the state monad in the belly of the SignalGenerator
. Is there a functional design pattern to achieve this?
EDIT
Based on Tomas's comment, I have worked on a toy model. The choice of having a strategy class is based on the need to wrap together a number of functions.
/////////////////////////////////////////////////////////////////////////////////////
// Definition of the state
/////////////////////////////////////////////////////////////////////////////////////
type StateFunc<'State, 'T> = 'State -> 'T * 'State
/////////////////////////////////////////////////////////////////////////////////////
// Definition of the State monad type
/////////////////////////////////////////////////////////////////////////////////////
type StateMonadBuilder<'State>() =
// M<'T> -> M<'T>
member b.ReturnFrom a : StateFunc<'State, 'T> = a
// 'T -> M<'T>
member b.Return a : StateFunc<'State, 'T> = ( fun s -> a, s)
// M<'T> * ('T -> M<'U>) -> M<'U>
member b.Bind(p : StateFunc<_, 'T>, rest : 'T -> StateFunc<_,_>) : StateFunc<'State, 'U> =
(fun s ->
let a, s' = p s
rest a s')
// Getter for the whole state, this type signature is because it passes along the state & returns the state
member b.getState : StateFunc<'State, _> = (fun s -> s, s)
// Setter for the state
member b.putState (s:'State) : StateFunc<'State, _> = (fun _ -> (), s)
/////////////////////////////////////////////////////////////////////////////////////
// The actual example
/////////////////////////////////////////////////////////////////////////////////////
let state = StateMonadBuilder<int> ()
// DoubleFunctOne defines standard operations that remain always the same
type Strategy (functOne) =
member this.DoubleFunctOne (x: int) = state {
let! res = functOne x
return res * 2 }
// I introduce customization with the definition of this function.
// Whenever I need, I will swap the function with some other
let myFunctOne x = state {
let someOtherFun x = x + 10
let! currState = state.getState
return currState * someOtherFun x}
// Here I mount the custom function on the strategy class, so the Strategy.DoubleFunctOne can produce a result
// In order to do so, I need to keep the construction in the state monad
let strategy1 = state {
return Strategy (myFunctOne) }
// Here begins the client side. The client will consume the methods provided by my strategies.
// He should not be concerned by the construction of the strategies
// Ok, then, let's put our work in production
let test1 = (state {
let! strategy = strategy1
return! strategy.DoubleFunctOne 10 }) 9
I was wondering if there would be a pattern solution where the Strategy
class could consume the mounted functions without nesting the state monad in its belly. In other words, is there a way to postpone the definition of the let state = StateMonadBuilder<int> ()
, without landing in a type inference headache?
I am relatively new to functional programming and F#. Please let me know if my question even makes sense! Thanks.