Implement me a strategy pattern for applying different discount calculations to a price, using an abstract function and two dynamic function strategies.
← Design Patterns and Idioms · Ref: Q1238
EK9 implements the strategy pattern using an abstract function as the strategy interface and dynamic functions as concrete strategies. A separate function takes the value AND the strategy as parameters, calling the strategy through the function-call syntax.
ABSTRACT FUNCTION AS STRATEGY
Discounter() as pure abstract -> price as Float <- rtn as Float?
DYNAMIC FUNCTION STRATEGIES
Each concrete strategy is a dynamic function with the signature of the abstract:
tenPercent <- () is Discounter as pure function rtn: price * 0.9
twentyPercent <- () is Discounter as pure function rtn: price * 0.8
The '() is Discounter as pure function' form creates an instance of the abstract function with a concrete body. The body uses 'rtn:' to assign the return variable.
PURE FUNCTION THAT TAKES A STRATEGY
The strategy is passed as a parameter typed by the abstract function:
applyDiscount() as pure -> price as Float strategy as Discounter <- rtn as Float: strategy(price)
Calling 'strategy(price)' invokes whichever concrete strategy was passed in.
USAGE
full <- 100.0 cheaper <- applyDiscount(full, tenPercent) cheapest <- applyDiscount(full, twentyPercent) stdout.println(`Full: ${full}, 10% off: ${cheaper}, 20% off: ${cheapest}`)
KEY ADVANTAGES
No class hierarchy needed. The abstract function defines the contract; dynamic functions provide implementations; the consumer just calls the parameter as a function. This is more concise than the Java/C# strategy interface + class implementations approach.
See Q214 for strategy pattern basics. See Q57 for strategy without subclassing. See Q52 for dynamic functions.
Example
defines module qa.patterns.discountstrategy defines function Discounter() as pure abstract -> price as Float <- rtn as Float? applyDiscount() as pure -> price as Float strategy as Discounter <- rtn as Float: strategy(price) defines program DiscountStrategyDemo() stdout <- Stdout() tenPercent <- () is Discounter as pure function rtn: price * 0.9 twentyPercent <- () is Discounter as pure function rtn: price * 0.8 full <- 100.0 cheaper <- applyDiscount(full, tenPercent) cheapest <- applyDiscount(full, twentyPercent) stdout.println(`Full: ${full}, 10% off: ${cheaper}, 20% off: ${cheapest}`)
Common mistakes
E07110 — An abstract function with no body must be declared 'as abstract' (or 'as pure abstract'). Using 'as open' is for functions WITH a body that can be overridden. See ek9 -h E07110 for details.
Incorrect:
Discounter() as open
Correct:
Discounter() as pure abstract
Other ways to ask this
- Create a Discounter strategy with two implementations and a function that applies the chosen strategy.
- Show me how to swap discount algorithms at runtime using EK9 abstract functions.
- Write a price calculator that takes a discount strategy as a parameter.
- Build a strategy pattern example for ten-percent and twenty-percent discounts.
Coming from another language?
Java: Strategy interface with implementing classes (DiscountStrategy interface, TenPercentDiscount implements). Python: pass functions directly (first-class). Kotlin: function types ((Double) -> Double) or interface implementations. Rust: trait objects (Box<dyn Discounter>) or closures. Go: function types. EK9: abstract function as the contract and dynamic functions as implementations — more concise than interface+class but with explicit type contract.
Keywords: callback, abstract function, behaviour parameterisation, strategy, swap algorithm, discount, pattern, dynamic function