1
1
mirror of https://github.com/thma/LtuPatternFactory.git synced 2025-01-05 19:04:46 +03:00
This commit is contained in:
Mahler, Thomas 2018-11-26 12:12:00 +01:00
parent 59d0e81ae1
commit 544f5f89c4
2 changed files with 69 additions and 40 deletions

View File

@ -7,6 +7,7 @@ Recently, while re-reading through the [Typeclassopedia](https://wiki.haskell.or
By searching the web I found some blog entries studying specific patterns, but I did not come across any comprehensive study. As it seemed that nobody did this kind of work yet I found it worthy to spend some time on it and write down all my findings on the subject.
I think this kind of exposition could be helpful if you are either:
* a programmer with an OO background who wants to get a better grip on how to implement complexer designs in functional programming
* a functional programmer who wants to get a deeper intuition for type classes.
@ -14,29 +15,31 @@ I think this kind of exposition could be helpful if you are either:
> Please use the [Issue Tracker](https://github.com/thma/LtuPatternFactory/issues) to enter your requests.
>
>Directions I'd like to cover in more depths are for instance:
> - complete coverage of the GOF set of patterns
> - coverage of category theory based patterns (any ideas are welcome!)
> - coverage of patterns with a clear FP background, eg. MapReduce, Blockchain, Function-as-a-service
>
> * complete coverage of the GOF set of patterns
> * coverage of category theory based patterns (any ideas are welcome!)
> * coverage of patterns with a clear FP background, eg. MapReduce, Blockchain, Function-as-a-service
# Table of contents
- [Lambda the ultimate pattern factory](#lambda-the-ultimate-pattern-factory)
- [The Patternopedia](#the-patternopedia)
- [Strategy -> Functor](#strategy---functor)
- [Singleton -> Applicative](#singleton---applicative)
- [Pipeline -> Monad](#pipeline---monad)
- [NullObject -> Maybe Monad](#nullobject---maybe-monad)
- [Composite -> SemiGroup -> Monoid](#composite---semigroup---monoid)
- [Visitor -> Foldable](#visitor---foldable)
- [Iterator -> Traversable](#iterator---traversable)
- [Type classes Category, Arrow & Co.](#type-classes-category-arrow--co)
- [Beyond type class patterns](#beyond-type-class-patterns)
- [Dependency Injection -> Parameter Binding](#dependency-injection---parameter-binding)
- [Adapter -> Function Composition](#adapter---function-composition)
- [Template Method -> type class default functions](#template-method---type-class-default-functions)
- Factory -> TBD
- [Abstract Factory -> functions as data type values](#abstract-factory---functions-as-data-type-values)
- [Builder -> record syntax, smart constructor](#builder---record-syntax-smart-constructor)
- [Some related links](#some-interesting-links)
* [Lambda the ultimate pattern factory](#lambda-the-ultimate-pattern-factory)
* [The Patternopedia](#the-patternopedia)
* [Strategy -> Functor](#strategy---functor)
* [Singleton -> Applicative](#singleton---applicative)
* [Pipeline -> Monad](#pipeline---monad)
* [NullObject -> Maybe Monad](#nullobject---maybe-monad)
* [Composite -> SemiGroup -> Monoid](#composite---semigroup---monoid)
* [Visitor -> Foldable](#visitor---foldable)
* [Iterator -> Traversable](#iterator---traversable)
* [Type classes Category, Arrow & Co.](#type-classes-category-arrow--co)
* [Beyond type class patterns](#beyond-type-class-patterns)
* [Dependency Injection -> Parameter Binding](#dependency-injection---parameter-binding)
* [Adapter -> Function Composition](#adapter---function-composition)
* [Template Method -> type class default functions](#template-method---type-class-default-functions)
* Factory -> TBD
* [Abstract Factory -> functions as data type values](#abstract-factory---functions-as-data-type-values)
* [Builder -> record syntax, smart constructor](#builder---record-syntax-smart-constructor)
* [Some related links](#some-interesting-links)
# The Patternopedia
@ -45,8 +48,8 @@ The [Typeclassopedia](https://wiki.haskell.org/wikiupload/8/85/TMR-Issue13.pdf)
In this section I'm taking a tour through the Typeclassopedia from a design pattern perspective.
For each of the Typeclassopedia type classes (at least up to Traversable) I try to explain how it corresponds to structures applied in design patterns.
## Strategy -> Functor
> "The strategy 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"
![strategy pattern](https://upload.wikimedia.org/wikipedia/commons/4/45/W3sDesign_Strategy_Design_Pattern_UML.jpg)
@ -71,16 +74,20 @@ context :: Num a => (a -> a) -> [a] -> [a]
context f l = map f l
-- using pointfree style this can be written as:
context = map
```
```
The `context` function uses higher order `map` function (`map :: (a -> b) -> [a] -> [b]`) to apply the strategies to lists of numbers:
```haskell
ghci> context strategyId [1..10]
[1,2,3,4,5,6,7,8,9,10]
ghci> context strategyDouble [1..10]
[2,4,6,8,10,12,14,16,18,20]
```
Instead of map we could use just any other function that accepts a function of type `Num a => a -> a` and applies it in a given context.
In Haskell the application of a function in a computational context is generalized with the type class `Functor`:
```haskell
class Functor f where
fmap :: (a -> b) -> f a -> f b
@ -916,6 +923,7 @@ This example has been implemented according to ideas presented in the paper
## Type classes Category, Arrow & Co.
Theses type classes aim at generalizing elements of Monads or Functors.
If you have ideas how these type classes map to specific design patterns please let me know!
@ -923,13 +931,15 @@ If you have ideas how these type classes map to specific design patterns please
# Beyond type class patterns
TBD:
- Chain of Responsibility: ADT + pattern matching the ADT (at least the distpatch variant)
- Currying / Partial application
- Partial application
- Blockchain as Monadic chain of Actions
## Dependency Injection -> Parameter Binding
> [...] Dependency injection is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
>
> This fundamental requirement means that using values (services) produced within the class from new or static methods is prohibited. The client should accept values passed in from outside. This allows the client to make acquiring dependencies someone else's problem.
@ -937,6 +947,7 @@ TBD:
In functional languages this is simply achieved by binding a functions formal parameters to values.
See the following example where the function `generatePage :: (String -> Html) -> String -> Html` does not only require a String input but also a rendering function that does the actual conversion from text to Html.
```haskell
data Html = ...
@ -946,7 +957,9 @@ generatePage renderer text = renderer text
htmlRenderer :: String -> Html
htmlRenderer = ...
```
With partial application its even possible to form a closure that incorporates the rendering function:
```haskell
ghci> closure = generatePage htmlRenderer
:type closure
@ -956,6 +969,7 @@ closure :: String -> Html
## Adapter -> Function Composition
> "The adapter pattern is a software design pattern (also known as wrapper, an alternative naming shared with the decorator pattern) that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code."
> (Quoted from https://en.wikipedia.org/wiki/Adapter_pattern)
@ -964,29 +978,38 @@ An example is an adapter that converts the interface of a Document Object Model
What does an adapter do? It translates a call to the adapter into a call of the adapted backend code. Which may also involve translation of the argument data.
Say we have some `backend` function that we want to provide with an adapter. we assume that `backend` has type `c -> d`:
```haskell
backend :: c -> d
```
Our adapter should be of type `a -> b`:
```haskell
adapter :: a -> b
```
In order to write this adapter we have to write two function. The first is:
```haskell
marshal :: a -> c
```
which translated the input argument of `adapter` into the correct type `c` that can be digested by the backend.
And the second function is:
```haskell
unmarshal :: d -> b
```
which translates the result of the `backend`function into the correct return type of `adapter`.
`adapter` will then look like follows:
```haskell
adapter :: a -> b
adapter = unmarshal . backend . marshal
```
So in essence the Adapter Patterns is just function composition.
Here is a simple example. Say we have a backend that understands only 24 hour arithmetics (eg. 23:50 + 0:20 = 0:10).
@ -1010,7 +1033,7 @@ addMinutesToWallTime x (WallTime (h, m)) =
let (dnew, hnew') = (hNew + 1) `quotRem` 24
in WallTime (24*dnew + hnew', mNew-60)
else WallTime (hNew, mNew)
-- this is our time representation in Minutes that we want to use in the frontend
newtype Minute = Minute Int deriving (Show)
@ -1051,6 +1074,7 @@ In functional programming the answer to this kind of problem is again the usage
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

View File

@ -1,4 +1,5 @@
module AbstractFactory where
import System.Info (os) -- provide Platform information
-- | representation of a Button UI widget
data Button = Button
@ -14,26 +15,30 @@ winPaint lbl = putStrLn $ "winButton: " ++ lbl
osxPaint :: String -> IO ()
osxPaint lbl = putStrLn $ "osxButton: " ++ lbl
-- | enumeration of supported operating system platforms
data OS = OSX | WIN deriving (Show, Eq, Enum)
data Platform = OSX | WIN | NIX | Other
platform :: Platform
platform = case os of
"darwin" -> OSX
"mingw32" -> WIN
"linux" -> NIX
_ -> Other
-- | create a button for os platform with label lbl
createButtonFor :: OS -> String -> Button
createButtonFor os lbl =
case os of
WIN -> Button lbl (winPaint lbl)
OSX -> Button lbl (osxPaint lbl)
createButton :: String -> Button
createButton lbl = case platform of
OSX -> Button lbl (osxPaint lbl)
WIN -> Button lbl (winPaint lbl)
NIX -> Button lbl (osxPaint lbl)
Other -> Button lbl (osxPaint lbl)
abstractFactoryDemo = do
putStrLn "AbstractFactory -> functions as data type values"
let os = WIN
let createButton = createButtonFor os
let ok = createButton "OK"
putStrLn "AbstractFactory -> functions as data type values"
let exit = createButton "Exit"
let ok = createButton "OK"
paint ok
paint exit
paint $ createButtonFor OSX "about"
let linuxButton = Button "penguin" (putStrLn "linuxButton: penguin")
paint linuxButton
let osxButton = Button "apple" (putStrLn "osxButton: apple")
paint osxButton