~~DISCUSSION~~
====== Monads ======
===== Example 1a =====
* this example shows how alternative results (sum type, algebraic type, see type A, B and C in the example) can be handled and propagated in a computation by means of 'case' / 'of' constructions
* it works but it is quite some code
* Code:
main :: IO ()
main =
do
putStrLn $ show (fAListToC [(A 12), (A 7), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A 7), (A 13), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A (-1)), (A 7), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A 7), (A 11), (A 5), (A 13)] fAToB)
data A a = A a | AError | ANone
deriving Show
data B a = B a | BError | BNone
deriving Show
data C a = C a | CError | CNone
deriving Show
fAToB :: A Int -> B Int
fAToB (A n)
| n <= 0 = BNone
| n < 13 = B (23 `div` n)
| otherwise = BError
fAToB AError = BError
fAToB ANone = BNone
fAListToC :: [A a] -> (A a -> B a) -> C [a]
fAListToC [] _ = C []
fAListToC (a@(A _):lrAList) fAToB' =
do
case fAToB' a of
B b ->
let
c = (fAListToC lrAList fAToB')
in
case c of
C lb -> C (b : lb)
CNone -> CNone
CError -> CError
BError -> CError
BNone -> CNone
fAListToC (AError:_) _ = CError
fAListToC (ANone:_) _ = CNone
* Output:
C [1,3,2,4]
CError
CNone
CError
===== Example 1b =====
* this example does not explain monads but an alternative solution where monads usually apply
* it shows how alternative results (sum type, algebraic type, see type A, B and C in the example) can be handled and propagated in a computation by means of a function that handles such alternative results
* that function handles such results by function 'ifC' and makes the job for a 'Monad' of type 'C'
* more detailed description of the program
* the function 'fAListToC' takes a list of 'A's (e.g. '[(A 12), (A 7), (A 11), (A 5)]') and transforms it to 'C' with a list of transformed data
* the transformation of data is according to function 'fAToB'
* 'fAToB' divides 23 by the number to be transformed, unless the number is 13 or greater then an error propagates ('BError'), and unless the number is 0 or smaler then no data propagates ('BNone')
* in 'fAListToC' 'BError' propagates to 'CError' and 'BNone' propagates to 'CNone'
* this example compile warning free with GHC 9.2.7 using -Wall
* Code:
main :: IO ()
main =
do
putStrLn $ show (fAListToC [(A 12), (A 7), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A 7), (A 13), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A (-1)), (A 7), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A 7), (A 11), (A 5), (A 13)] fAToB)
data A a = A a | AError | ANone
deriving Show
data B a = B a | BError | BNone
deriving Show
data C a = C a | CError | CNone
deriving Show
fAToB :: A Int -> B Int
fAToB (A n)
| n <= 0 = BNone
| n < 13 = B (23 `div` n)
| otherwise = BError
fAToB AError = BError
fAToB ANone = BNone
fAListToC :: [A a] -> (A a -> B a) -> C [a]
fAListToC [] _ = C []
fAListToC (a@(A _):lrAList) fAToB' =
do
case fAToB' a of
B b -> ifC (fAListToC lrAList fAToB' ) (\lb -> C (b : lb)) CNone CError
BError -> CError
BNone -> CNone
fAListToC (AError:_) _ = CError
fAListToC (ANone:_) _ = CNone
ifC :: C a -> (a -> b) -> b -> b -> b
ifC (C x) fThen _ _ = fThen x
ifC CNone _ bNone _ = bNone
ifC CError _ _ bError = bError
* Output:
C [1,3,2,4]
CError
CNone
CError
===== Example 1c =====
* this example uses the instances for Functor, Applicative and Monad
* this way the operator '>>=' can be used
* Code:
import qualified Control.Monad as M (liftM, ap)
instance Functor C where
fmap = M.liftM
instance Applicative C where
pure = C
(<*>) = M.ap
instance Monad C where
CError >>= _ = CError
CNone >>= _ = CNone
(C x) >>= f = f x
return = pure
main :: IO ()
main =
do
putStrLn $ show (fAListToC [(A 12), (A 7), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A 7), (A 13), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A (-1)), (A 7), (A 11), (A 5)] fAToB)
putStrLn $ show (fAListToC [(A 7), (A 11), (A 5), (A 13)] fAToB)
data A a = A a | AError | ANone
deriving Show
data B a = B a | BError | BNone
deriving Show
data C a = C a | CError | CNone
deriving Show
fAToB :: A Int -> B Int
fAToB (A n)
| n <= 0 = BNone
| n < 13 = B (23 `div` n)
| otherwise = BError
fAToB AError = BError
fAToB ANone = BNone
fAListToC :: [A a] -> (A a -> B a) -> C [a]
fAListToC [] _ = C []
fAListToC (a@(A _):lrAList) fAToB' =
do
case fAToB' a of
B b -> fAListToC lrAList fAToB' >>= \lb -> return (b : lb)
BError -> CError
BNone -> CNone
fAListToC (AError:_) _ = CError
fAListToC (ANone:_) _ = CNone
* Output:
C [1,3,2,4]
CError
CNone
CError
===== Example 2 =====
* inspired from https://wiki.haskell.org/All_About_Monads#A_physical_analogy_for_monads
* examples compile warning free with GHC 8.10.4 using -Wall
* Code:
module Main where
-- import liftM, and ap to create a Tray monad
import qualified Control.Monad as M (liftM, ap)
main :: IO ()
main = do
-- execute the assemply line and print the result starting with a log that is 300 mm in diameter, and 250 mm long
print $ assemblyLine (Log 300 250)
-- the same with other diameter, and length
print $ assemblyLine (Log 200 250)
-- the same with other diameter, and length
print $ assemblyLine (Log 24 250)
-- the same with other diameter, and length
print $ assemblyLine (Log 250 99)
-- the same with other diameter, and length
print $ assemblyLine (Log 22 201)
-- the same with other diameter, and length
print $ assemblyLine (Log 29 201)
-- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv --
-- the analogy of an assambly line
assemblyLine :: Wood -> Tray Packed
assemblyLine w =
do
c <- makeChopsticks w -- Wood to Chopsticks
c' <- polishChopsticks c -- ...to polished Chopsticks
c'' <- wrapChopsticks c' -- ...to packed Chopsticks
return c''
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ --
--------------------------------------------------------------------------------
-- Basic types
--------------------------------------------------------------------------------
-- Wood
data Wood = Log { nDiameterInMM :: Float, nLengthInMM :: Float }
deriving Show
--------------------------------------------------------------------------------
-- Chopsticks and its Quality
data Quality = Sawn | Polished
deriving Show
data Chopsticks = Chopsticks { nPieces :: Integer, nQuality :: Quality }
deriving Show
--------------------------------------------------------------------------------
-- Packed with Wrapper or in a StorageBin
data Packed = Wrapper Chopsticks | StorageBin Chopsticks
deriving Show
--------------------------------------------------------------------------------
-- worker functions of the basic types
-- NOTE: All of them can have different in put, but the output is always a Tray.
-- NOTE: See the data type Tray.
-- makes roughly shaped chopsticks out of wood
makeChopsticks :: Wood -> Tray Chopsticks
makeChopsticks w
| nGain > 0 = Contains (Chopsticks nGain Sawn)
| (nLengthInMM w) < 200 = Empty "to short"
| otherwise = Empty "to small in diameter"
where
nGain
| (nLengthInMM w) >= 200 = (round ((((nDiameterInMM w) - 20) * (nLengthInMM w)) / (220 * 5)))
| otherwise = 0
-- polishes chopsticks
polishChopsticks :: Chopsticks -> Tray Chopsticks
polishChopsticks c
| amount >= 1 = Contains (Chopsticks amount Polished)
| otherwise = Empty "no chopsticks after polishing"
where
amount = floor (((fromIntegral (nPieces c)) :: Double) * 0.9)
-- wraps chopsticks
wrapChopsticks :: Chopsticks -> Tray Packed
wrapChopsticks c
| amount >= 50 = Contains (Wrapper (Chopsticks amount (nQuality c)))
| amount <= 0 = Empty "no chopsticks at packing"
| otherwise = Contains (StorageBin (Chopsticks amount (nQuality c)))
where
amount = floor (((fromIntegral (nPieces c)) :: Double) * 0.95)
--------------------------------------------------------------------------------
-- Tray
-- trays are either empty or contain a single item
data Tray x = Empty String | Contains x
deriving Show
--------------------------------------------------------------------------------
-- Tray as instance of Monad
-- Tray is a monad
instance Functor Tray where
fmap = M.liftM
instance Applicative Tray where
pure = return
(<*>) = M.ap
instance Monad Tray where
Empty s >>= _ = Empty s
(Contains x) >>= worker = worker x
return = Contains
* Output:
Contains (Wrapper (Chopsticks {nPieces = 54, nQuality = Polished}))
Contains (StorageBin (Chopsticks {nPieces = 34, nQuality = Polished}))
Empty "no chopsticks after polishing"
Empty "to short"
Empty "to small in diameter"
Empty "no chopsticks at packing"