1
1
mirror of https://github.com/thma/LtuPatternFactory.git synced 2024-12-02 08:33:20 +03:00

on the ways

This commit is contained in:
thma 2018-10-20 22:10:42 +02:00
parent 4ed39bfa05
commit 7fda5d425d
3 changed files with 206 additions and 12 deletions

143
README.md
View File

@ -752,16 +752,153 @@ In functional programming the null object pattern is typically formalized with o
> [...] an option type or maybe type is a polymorphic type that represents encapsulation of an optional value; e.g., it is used as the return type of functions which may or may not return a meaningful value when they are applied. It consists of a constructor which either is empty (named None or `Nothing`), or which encapsulates the original data type `A` (written `Just A` or Some A).
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Option_type)
In Haskell the most simple option type is `Maybe`. Let's directly dive into an instructive example:
In Haskell the most simple option type is `Maybe`. Let's directly dive into an example. We define a reverse index, mapping songs to album titles.
If we now lookup up a song title we may either be lucky and find the respective album or not so lucky when there is no album matching our song:
```haskell
import Data.Map (Map, fromList)
import qualified Data.Map as Map (lookup) -- avoid clash with Prelude.lookup
-- type aliases for Songs and Albums
type Song = String
type Album = String
-- the simplified reverse song index
songMap :: Map Song Album
songMap = fromList
[("Baby Satellite","Microgravity")
,("An Ending", "Apollo: Atmospheres and Soundtracks")]
```
We can lookup this map by using the function `Map.lookup :: Ord k => k -> Map k a -> Maybe a`.
If no match is found it will return `Nothing` if a match is found it will return `Just match`:
```haskell
ghci> Map.lookup "Baby Satellite" songMap
Just "Microgravity"
ghci> Map.lookup "The Fairy Tale" songMap
Nothing
```
Actually the `Maybe`type is simply defined as:
```haskell
data Maybe a = Nothing | Just a
deriving (Eq, Ord)
```
All code using the `Map.lookup` function will never be confronted with any kind of Exceptions, null pointers or other nasty things. Even in case of errors lookup will allways return a properly typed `Maybe` instance. By pattern matching for `Nothing` or `Just a` client code can react on failing matches or positive results:
```haskell
case Map.lookup "Ancient Campfire" songMap of
Nothing -> print "sorry, could not find your song"
Just a -> print a
```
Let's try to apply this to an extension of our simple song lookup.
Let's assume that our music database has much more information available. Apart from a reverse index from songs to albums, there might also be an index mapping album titles to artists.
And we might also have an index mapping artist names to their websites:
```haskell
type Song = String
type Album = String
type Artist = String
type URL = String
songMap :: Map Song Album
songMap = fromList
[("Baby Satellite","Microgravity")
,("An Ending", "Apollo: Atmospheres and Soundtracks")]
albumMap :: Map Album Artist
albumMap = fromList
[("Microgravity","Biosphere")
,("Apollo: Atmospheres and Soundtracks", "Brian Eno")]
artistMap :: Map Artist URL
artistMap = fromList
[("Biosphere","http://www.biosphere.no//")
,("Brian Eno", "http://www.brian-eno.net")]
loookup' :: Ord a => Map a b -> a -> Maybe b
loookup' = flip Map.lookup
findAlbum :: Song -> Maybe Album
findAlbum = loookup' songMap
findArtist :: Album -> Maybe Artist
findArtist = loookup' albumMap
findWebSite :: Artist -> Maybe URL
findWebSite = loookup' artistMap
```
With all this information at hand we want to write a function that has an input parameter of type `Song` and returns a `Maybe URL` by going from song to album to artist to website url:
```haskell
findUrlFromSong :: Song -> Maybe URL
findUrlFromSong song =
case findAlbum song of
Nothing -> Nothing
Just album ->
case findArtist album of
Nothing -> Nothing
Just artist ->
case findWebSite artist of
Nothing -> Nothing
Just url -> Just url
```
This code makes use of the pattern matching logic described before. It's worth to note that there is some nice circuit breaking happening in case of a `Nothing`. In this case `Nothing` is directly returned as a result and the rest of the case-ladder is not executed.
What's not so nice is:
>the dreaded ladder of code marching off the right of the screen
> [Quoted from Real World Haskell](http://book.realworldhaskell.org/)
The good news is that it is possible to avoid this ladder by utilizing the Monad features of `Maybe`.
We can rewrite our search by applying the `andThen` operator `>>=` as Maybe is an instance of Monad:
```haskell
findUrlFromSong' :: Song -> Maybe URL
findUrlFromSong' song =
findAlbum song >>= \album ->
findArtist album >>= \artist ->
findWebSite artist
```
or even shorter as we can eliminate the lambda notation due to eta-conversion (https://wiki.haskell.org/Eta_conversion):
```haskell
findUrlFromSong'' :: Song -> Maybe URL
findUrlFromSong'' song =
findAlbum song >>= findArtist >>= findWebSite
```
Using it in GHCi:
```haskell
ghci> findUrlFromSong'' "All you need is love"
Nothing
ghci> findUrlFromSong'' "An Ending"
Just "http://www.brian-eno.net"
```
Here is how `Maybe` implements the Monad logic:
```haskell
- | @since 2.01
instance Applicative Maybe where
pure = Just
Just f <*> m = fmap f m
Nothing <*> _m = Nothing
liftA2 f (Just x) (Just y) = Just (f x y)
liftA2 _ _ _ = Nothing
Just _m1 *> m2 = m2
Nothing *> _m2 = Nothing
-- | @since 2.01
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
(>>) = (*>)
fail _ = Nothing
```
- also explain how to avoid "staircase of death" with Maybe
http://blog.ploeh.dk/2018/04/23/null-object-as-identity/
## Blockchain -> State Monad

View File

@ -21,4 +21,4 @@ main = do
adapterDemo
builderDemo
templateMethodDemo
nullObjectDemo

View File

@ -1,20 +1,77 @@
module NullObject where
import Data.Map
import qualified Data.Map as Map
import Data.Map (Map, fromList)
import qualified Data.Map as Map (lookup) -- avoid clash with Prelude.lookup
type Song = String
type Album = String
type Artist = String
type URL = String
songDB :: Map Song Album
songDB = Map.fromList
[("no woman no cry","Kaya")]
songMap :: Map Song Album
songMap = fromList
[("Baby Satellite","Microgravity")
,("An Ending", "Apollo: Atmospheres and Soundtracks")]
albumMap :: Map Album Artist
albumMap = fromList
[("Microgravity","Biosphere")
,("Apollo: Atmospheres and Soundtracks", "Brian Eno")]
artistMap :: Map Artist URL
artistMap = fromList
[("Biosphere","http://www.biosphere.no//")
,("Brian Eno", "http://www.brian-eno.net")]
loookup' :: Ord a => Map a b -> a -> Maybe b
loookup' = flip Map.lookup
findAlbum :: Song -> Maybe Album
findAlbum song = Map.lookup song songDB
findAlbum = loookup' songMap
findArtist :: Album -> Maybe Artist
findArtist = loookup' albumMap
findWebSite :: Artist -> Maybe URL
findWebSite = loookup' artistMap
findUrlFromSong :: Song -> Maybe URL
findUrlFromSong song =
case findAlbum song of
Nothing -> Nothing
Just album ->
case findArtist album of
Nothing -> Nothing
Just artist ->
case findWebSite artist of
Nothing -> Nothing
Just url -> Just url
findUrlFromSongDo :: Song -> Maybe URL
findUrlFromSongDo song = do
album <- findAlbum song
artist <- findArtist album
return findWebSite artist
findUrlFromSong' :: Song -> Maybe URL
findUrlFromSong' song =
findAlbum song >>= \album ->
findArtist album >>= \artist ->
findWebSite artist
findUrlFromSong'' :: Song -> Maybe URL
findUrlFromSong'' song =
findAlbum song >>= findArtist >>= findWebSite
nullObjectDemo = do
putStrLn "NullObject -> Maybe"
print $ findAlbum "no woman no cry"
print $ findAlbum "a ram sam sam"
print $ Map.lookup "Baby Satellite" songMap
print $ Map.lookup "The Fairy Tale" songMap
case Map.lookup "Ancient Campfire" songMap of
Nothing -> print "sorry, could not find your song"
Just s -> print s
print $ findUrlFromSong' "An Ending"
print $ findUrlFromSong'' "Baby Satellite"