mirror of
https://github.com/thma/LtuPatternFactory.git
synced 2025-01-07 11:57:50 +03:00
completing template method section
This commit is contained in:
parent
33efee5263
commit
86270ea2dd
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,3 +22,4 @@ cabal.project.local~
|
|||||||
.ghc.environment.*
|
.ghc.environment.*
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
cabal.config
|
||||||
|
200
README.md
200
README.md
@ -503,91 +503,11 @@ TBD: Traversable Demo
|
|||||||
### Alternative approaches
|
### Alternative approaches
|
||||||
http://blog.ploeh.dk/2018/06/25/visitor-as-a-sum-type/
|
http://blog.ploeh.dk/2018/06/25/visitor-as-a-sum-type/
|
||||||
|
|
||||||
## Template Method -> Typeclass default functions
|
|
||||||
|
|
||||||
> In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses.
|
|
||||||
> It lets one redefine certain steps of an algorithm without changing the algorithm's structure.
|
|
||||||
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
|
|
||||||
|
|
||||||
The TemplateMethod pattern is quite similar to the [StrategyPattern](#strategy---functor). The main difference is the level of granularity.
|
|
||||||
In Strategy a complete block of functionality - the Strategy - can be replaced.
|
|
||||||
In TemplateMethod the overall layout of an algorithm is predefined and only specific parts of it may be replaced.
|
|
||||||
|
|
||||||
In functional programming the answer to this kind of problem is again the usage of higher order functions.
|
|
||||||
|
|
||||||
In the following example we come back to the example for the [Adapter](#adapter---function-composition).
|
|
||||||
The function `addMinutesAdapter` lays out a structure for interfacing to some kind of backend:
|
|
||||||
1. marshalling the arguments into the backend format
|
|
||||||
2. apply the backend logic to the marshalled arguments
|
|
||||||
3. unmarshal the backend result data into the frontend format
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
addMinutesAdapter :: Int -> Minute -> Minute
|
|
||||||
addMinutesAdapter x = unmarshalWM . addMinutesToWallTime x . marshalMW
|
|
||||||
```
|
|
||||||
|
|
||||||
In this code the backend functionality - `addMinutesToWallTime` - is a hardcoded part of the overall structure.
|
|
||||||
|
|
||||||
Let's assume we want to use different kind of backend implementations - for instance a mock replacement.
|
|
||||||
In this case we would like to keep the overall structure - the template - and would just make a specific part of it flexible.
|
|
||||||
This sounds like an ideal candidate for the TemplateMethod pattern:
|
|
||||||
```haskell
|
|
||||||
addMinutesTemplate :: (Int -> WallTime -> WallTime) -> Int -> Minute -> Minute
|
|
||||||
addMinutesTemplate f x =
|
|
||||||
unmarshalWM .
|
|
||||||
f x .
|
|
||||||
marshalMW
|
|
||||||
```
|
|
||||||
`addMinutesTemplate` has an additional parameter f of type `(Int -> WallTime -> WallTime)`. This parameter may be bound to `addMinutesToWallTime` or alternative implementations:
|
|
||||||
```haskell
|
|
||||||
-- implements linear addition (the normal case) even for values > 1440
|
|
||||||
linearTimeAdd :: Int -> Minute -> Minute
|
|
||||||
linearTimeAdd = addMinutesTemplate addMinutesToWallTime
|
|
||||||
|
|
||||||
-- implements cyclic addition, respecting a 24 hour (1440 Min) cycle
|
|
||||||
cyclicTimeAdd :: Int -> Minute -> Minute
|
|
||||||
cyclicTimeAdd = addMinutesTemplate addMinutesToWallTime'
|
|
||||||
```
|
|
||||||
|
|
||||||
where `addMinutesToWallTime'` implements a silly 24 hour cyclic addition:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
-- a 24 hour (1440 min) cyclic version of addition: 1400 + 100 = 60
|
|
||||||
addMinutesToWallTime' :: Int -> WallTime -> WallTime
|
|
||||||
addMinutesToWallTime' x (WallTime (h, m)) =
|
|
||||||
let (hAdd, mAdd) = x `quotRem` 60
|
|
||||||
hNew = h + hAdd
|
|
||||||
mNew = m + mAdd
|
|
||||||
in if mNew >= 60
|
|
||||||
then WallTime ((hNew + 1) `rem` 24, mNew-60)
|
|
||||||
else WallTime (hNew, mNew)
|
|
||||||
```
|
|
||||||
|
|
||||||
And here is how we use it to do actual computations:
|
|
||||||
```haskell
|
|
||||||
templateMethodDemo = do
|
|
||||||
putStrLn $ "linear time: " ++ (show $ linearTimeAdd 100 (Minute 1400))
|
|
||||||
putStrLn $ "cyclic time: " ++ (show $ cyclicTimeAdd 100 (Minute 1400))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Typeclass minimal implementations as template method
|
|
||||||
|
|
||||||
> The template method is used in frameworks, where each implements the invariant parts of a domain's architecture,
|
|
||||||
> leaving "placeholders" for customization options. This is an example of inversion of control.
|
|
||||||
> The template method is used for the following reasons:
|
|
||||||
>
|
|
||||||
> - Let subclasses implement varying behavior (through method overriding).
|
|
||||||
> - Avoid duplication in the code: the general workflow structure is implemented once in the abstract class's algorithm,
|
|
||||||
> and necessary variations are implemented in the subclasses.
|
|
||||||
> - Control at what point(s) subclassing is allowed. As opposed to a simple polymorphic override, where the base method
|
|
||||||
> would be entirely rewritten allowing radical change to the workflow, only the specific details of the workflow are
|
|
||||||
> allowed to change.
|
|
||||||
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
|
|
||||||
|
|
||||||
# Beyond Typeclass patterns
|
# Beyond Typeclass patterns
|
||||||
|
|
||||||
TBD:
|
TBD:
|
||||||
- Chain of Responsibility: ADT + fumction pattern matching the ADT (at least the distpatch variant)
|
- Chain of Responsibility: ADT + pattern matching the ADT (at least the distpatch variant)
|
||||||
|
|
||||||
- Currying / Partial application
|
- Currying / Partial application
|
||||||
|
|
||||||
@ -697,6 +617,124 @@ adapterDemo = do
|
|||||||
putStrLn ""
|
putStrLn ""
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Template Method -> Typeclass default functions
|
||||||
|
|
||||||
|
> In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses.
|
||||||
|
> It lets one redefine certain steps of an algorithm without changing the algorithm's structure.
|
||||||
|
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
|
||||||
|
|
||||||
|
The TemplateMethod pattern is quite similar to the [StrategyPattern](#strategy---functor). The main difference is the level of granularity.
|
||||||
|
In Strategy a complete block of functionality - the Strategy - can be replaced.
|
||||||
|
In TemplateMethod the overall layout of an algorithm is predefined and only specific parts of it may be replaced.
|
||||||
|
|
||||||
|
In functional programming the answer to this kind of problem is again the usage of higher order functions.
|
||||||
|
|
||||||
|
In the following example we come back to the example for the [Adapter](#adapter---function-composition).
|
||||||
|
The function `addMinutesAdapter` lays out a structure for interfacing to some kind of backend:
|
||||||
|
1. marshalling the arguments into the backend format
|
||||||
|
2. apply the backend logic to the marshalled arguments
|
||||||
|
3. unmarshal the backend result data into the frontend format
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
addMinutesAdapter :: Int -> Minute -> Minute
|
||||||
|
addMinutesAdapter x = unmarshalWM . addMinutesToWallTime x . marshalMW
|
||||||
|
```
|
||||||
|
|
||||||
|
In this code the backend functionality - `addMinutesToWallTime` - is a hardcoded part of the overall structure.
|
||||||
|
|
||||||
|
Let's assume we want to use different kind of backend implementations - for instance a mock replacement.
|
||||||
|
In this case we would like to keep the overall structure - the template - and would just make a specific part of it flexible.
|
||||||
|
This sounds like an ideal candidate for the TemplateMethod pattern:
|
||||||
|
```haskell
|
||||||
|
addMinutesTemplate :: (Int -> WallTime -> WallTime) -> Int -> Minute -> Minute
|
||||||
|
addMinutesTemplate f x =
|
||||||
|
unmarshalWM .
|
||||||
|
f x .
|
||||||
|
marshalMW
|
||||||
|
```
|
||||||
|
`addMinutesTemplate` has an additional parameter f of type `(Int -> WallTime -> WallTime)`. This parameter may be bound to `addMinutesToWallTime` or alternative implementations:
|
||||||
|
```haskell
|
||||||
|
-- implements linear addition (the normal case) even for values > 1440
|
||||||
|
linearTimeAdd :: Int -> Minute -> Minute
|
||||||
|
linearTimeAdd = addMinutesTemplate addMinutesToWallTime
|
||||||
|
|
||||||
|
-- implements cyclic addition, respecting a 24 hour (1440 Min) cycle
|
||||||
|
cyclicTimeAdd :: Int -> Minute -> Minute
|
||||||
|
cyclicTimeAdd = addMinutesTemplate addMinutesToWallTime'
|
||||||
|
```
|
||||||
|
|
||||||
|
where `addMinutesToWallTime'` implements a silly 24 hour cyclic addition:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
-- a 24 hour (1440 min) cyclic version of addition: 1400 + 100 = 60
|
||||||
|
addMinutesToWallTime' :: Int -> WallTime -> WallTime
|
||||||
|
addMinutesToWallTime' x (WallTime (h, m)) =
|
||||||
|
let (hAdd, mAdd) = x `quotRem` 60
|
||||||
|
hNew = h + hAdd
|
||||||
|
mNew = m + mAdd
|
||||||
|
in if mNew >= 60
|
||||||
|
then WallTime ((hNew + 1) `rem` 24, mNew-60)
|
||||||
|
else WallTime (hNew, mNew)
|
||||||
|
```
|
||||||
|
|
||||||
|
And here is how we use it to do actual computations:
|
||||||
|
```haskell
|
||||||
|
templateMethodDemo = do
|
||||||
|
putStrLn $ "linear time: " ++ (show $ linearTimeAdd 100 (Minute 1400))
|
||||||
|
putStrLn $ "cyclic time: " ++ (show $ cyclicTimeAdd 100 (Minute 1400))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typeclass minimal implementations as template method
|
||||||
|
|
||||||
|
> The template method is used in frameworks, where each implements the invariant parts of a domain's architecture,
|
||||||
|
> leaving "placeholders" for customization options. This is an example of inversion of control.
|
||||||
|
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
|
||||||
|
|
||||||
|
The Typeclasses in Haskells base library apply this template approach frequently to reduce the effort for implementing typeclass instances and to provide a predefined structure with specific 'customization options'.
|
||||||
|
|
||||||
|
As an example let's extend the type `WallTime` by an associative binary operation `addWallTimes` to form an instance of the `Monoid` typeclass
|
||||||
|
```haskell
|
||||||
|
addWallTimes :: WallTime -> WallTime -> WallTime
|
||||||
|
addWallTimes a@(WallTime (h,m)) b =
|
||||||
|
let aMin = h*60 + m
|
||||||
|
in addMinutesToWallTime aMin b
|
||||||
|
|
||||||
|
instance Semigroup WallTime where
|
||||||
|
(<>) = addWallTimes
|
||||||
|
instance Monoid WallTime where
|
||||||
|
mempty = WallTime (0,0)
|
||||||
|
```
|
||||||
|
Even though we specified only `mempty` and `(<>)` we can now use the functions `mappend :: Monoid a => a -> a -> a` and `mconcat :: Monoid a => [a] -> a` on WallTime instances:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
templateMethodDemo = do
|
||||||
|
let a = WallTime (3,20)
|
||||||
|
print $ mappend a a
|
||||||
|
print $ mconcat [a,a,a,a,a,a,a,a,a]
|
||||||
|
```
|
||||||
|
By looking at the definition of the `Monoid` typeclass we can see how this 'magic' is made possible:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
class Semigroup a => Monoid a where
|
||||||
|
-- | Identity of 'mappend'
|
||||||
|
mempty :: a
|
||||||
|
|
||||||
|
-- | An associative operation
|
||||||
|
mappend :: a -> a -> a
|
||||||
|
mappend = (<>)
|
||||||
|
|
||||||
|
-- | Fold a list using the monoid.
|
||||||
|
mconcat :: [a] -> a
|
||||||
|
mconcat = foldr mappend mempty
|
||||||
|
```
|
||||||
|
For `mempty` only a type requirement but no definition is given.
|
||||||
|
But for `mappend` and `mconcat` default implementations are provided.
|
||||||
|
So the Monoid typeclass definition forms a *template* where the default implementations define the 'invariant parts' of the typeclass and the part specified by us form the 'customization options'.
|
||||||
|
|
||||||
|
(please note that it's generally possible to override the default implementations)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## NullObject -> Maybe Monoid
|
## NullObject -> Maybe Monoid
|
||||||
- also explain how to avoid "staircase of death" with Maybe
|
- also explain how to avoid "staircase of death" with Maybe
|
||||||
http://blog.ploeh.dk/2018/04/23/null-object-as-identity/
|
http://blog.ploeh.dk/2018/04/23/null-object-as-identity/
|
||||||
|
@ -42,5 +42,6 @@ templateMethodDemo = do
|
|||||||
putStrLn $ "cyclic time: " ++ (show $ cyclicTimeAdd 100 (Minute 1400))
|
putStrLn $ "cyclic time: " ++ (show $ cyclicTimeAdd 100 (Minute 1400))
|
||||||
putStrLn ""
|
putStrLn ""
|
||||||
let a = WallTime (3,20)
|
let a = WallTime (3,20)
|
||||||
|
print $ mappend a a
|
||||||
print $ mconcat [a,a,a,a,a,a,a,a,a]
|
print $ mconcat [a,a,a,a,a,a,a,a,a]
|
||||||
putStrLn ""
|
putStrLn ""
|
||||||
|
Loading…
Reference in New Issue
Block a user