Disclaimer: I do not use functional languages; only trying to comprehend some parts of FP.
Google suggest the articles where first order functions with lambdas can offer the similar functionality which Strategy pattern gives.
Yet we somehow need to match data and corresponding lambda. With OO-design this is done automatically with Virtual Method Table (VMT), that is the type itself carries the important information needed to reason about execution flow making further addition of a new behavior easy (open closed principle): inherit and override. Old code simply stays unchanged. Functional pattern matching seems to be static in this regard and does not allow such kind of dynamics.
Sure, it is possible to code a configurable matching behavior for selecting lambda based on given data, but isn't it what we have in OOP out of the box?
In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.
The Strategy pattern lets you indirectly alter the object's behavior at runtime by associating it with different sub-objects which can perform specific sub-tasks in different ways. Use the Strategy when you have a lot of similar classes that only differ in the way they execute some behavior.
The idea behind the strategy pattern is that "algorithms can be selected at runtime." (Wikipedia, Strategy Pattern) The pattern, in essence, is selecting the right strategy (or behavior) for solving a particular problem at runtime. Hence, it's name.
The strategy pattern is used to solve problems that might (or is foreseen they might) be implemented or solved by different strategies and that possess a clearly defined interface for such cases.
The simplest way, which is what I think most people are referring to when they talk about higher-order functions replacing the strategy pattern, is to pass the strategy in as an argument to your common code. Here's a Scala example that executes a strategy on two numbers, then multiplies the result by 3:
def commonCode(strategy: (Int, Int) => Int)(arg1: Int, arg2: Int) : Int =
strategy(arg1, arg2) * 3
You define your various strategies like this:
def addStrategy(arg1: Int, arg2: Int) : Int = arg1 + arg2
def subtractStrategy(arg1: Int, arg2: Int) : Int = arg1 - arg2
Add call it like this:
commonCode(addStrategy)(2, 3) // returns 15
commonCode(subtractStrategy)(2, 3) // returns -3
You can use partial application to avoid having to pass the strategy all over the place:
val currentStrategy = addStrategy _
...
val currentCommon = commonCode(currentStrategy)_
currentCommon(2, 3) // returns 15
This is so common we don't call it a strategy or a pattern. It's just basic functional programming. The strategy
parameter to the commonCode
function is like any other data. You can put it into a data structure with a bunch of other functions. You can use a closure or partial application to associate additional strategy-specific data. You can use a lambda like commonCode(_ / _)
to avoid having to give your strategy a name.
Here are two ways of implementing a simple Strategy pattern in Haskell. This is based on a simple OO example. It doesn't implement the different behaviors, it just shows you where they'd go.
Ex. 1: using data structures with hooks. Note that you specify the behavior you want when you create the Robot. Here, I created constructors that define the different configurations of Robot I want. The downside of this: these different kinds of robots share the same structure, so their implementations may be coupled.
module Main where
data Robot = Robot {
moveImpl :: Robot -> IO Robot
}
move :: Robot -> IO Robot
move r = (moveImpl r) r
aggressiveMove :: Robot -> IO Robot
aggressiveMove r = putStrLn "Find another robot, then attack it!" >> return r
defensiveMove :: Robot -> IO Robot
defensiveMove r = putStrLn "Find another robot, then run away from it!" >> return r
aggressiveRobot :: Robot
aggressiveRobot = Robot aggressiveMove
defensiveRobot :: Robot
defensiveRobot = Robot defensiveMove
main = do
let robots = [aggressiveRobot, defensiveRobot]
mapM_ move robots
Ex. 2: using type classes. This allows you to take totally different structures, representing different behaviors, and make them work in a uniform way. The downside: you can't just put them in a list, since Robot is no longer a data type that binds all different kinds of robots together.
module Main where
class Robot r where
move :: r -> IO r
data AggressiveRobot = AggressiveRobot
aggressiveMove :: AggressiveRobot -> IO AggressiveRobot
aggressiveMove r = putStrLn "Find another robot, then attack it!" >> return r
instance Robot AggressiveRobot where
move = aggressiveMove
data DefensiveRobot = DefensiveRobot
defensiveMove :: DefensiveRobot -> IO DefensiveRobot
defensiveMove r = putStrLn "Find another robot, then run away from it!" >> return r
instance Robot DefensiveRobot where
move = defensiveMove
main = do
let robotA = AggressiveRobot
robotB = DefensiveRobot
move robotA
move robotB
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