6

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?

Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111
Pavel Voronin
  • 13,503
  • 7
  • 71
  • 137
  • 6
    All "Strategy" needs is higher-order functions. In fact, you can view Strategy's existence as compensating for the lack of higher-order functions in many OO languages, whereas FP languages have them "out of the box". – molbdnilo May 25 '15 at 14:48
  • @molbdnilo That's fine, but how then do you select the lambda needed for particular data? – Pavel Voronin May 25 '15 at 15:36
  • @voroninp With a conditional or something similar. Do you have a more concrete example? It would be easier to illustrate this if you gave a small, concrete example of some code that uses the strategy pattern to work from. – David Young May 25 '15 at 19:13
  • Related question: http://stackoverflow.com/questions/29195374/functional-way-to-implement-strategy-pattern – Fuhrmanator May 25 '15 at 19:18

2 Answers2

11

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.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • Well, I should probably reformulate the question. The most important part of strategy pattern is polymorphic behavior. In 'conventional' OO-design it is implemented via VMT, and it was not clear for me what surves as a substitution of VMT when we speak about FP. I think I've found the answer in multimethods/double dispatch. – Pavel Voronin May 28 '15 at 22:54
  • 2
    The strategy pattern doesn't actually require polymorphism, just functions with the same signature. Polymorphism is the workaround when your language doesn't easily support higher-order functions. How polymorphism is implemented in FP is really a separate question. – Karl Bielefeldt May 29 '15 at 02:16
  • Here is a good example and article about using currying (curry is a higher order function) to implement strategy pattern in FP way in Javascript: https://www.linkedin.com/pulse/strategy-pattern-functional-programming-vladim%C3%ADr-gorej/ – peterhil Oct 25 '18 at 10:02
1

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
Owen S.
  • 7,665
  • 1
  • 28
  • 44