-
Notifications
You must be signed in to change notification settings - Fork 5
/
StateMachine.hs
141 lines (124 loc) · 5.25 KB
/
StateMachine.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
module StateMachine where
-- | Author Pablo Perez Garcia
import Control.Monad (foldM)
import Data.Text (Text)
import Text.Printf (printf)
-- | State machine implementation
-- -------------------------------
{-| Alias Function for to avoid duplicate types in the function declaration.-}
type StateMachine state action = state -> action -> IO state
{-|Haskell has a very elegant way to do overload of functions, more than overload to catch invocation values
to assign to a specific function implementation.
In this case, depending of the State and Action arguments received for the function it will redirect to
a specific implementation, and in case we cannot find any, it will go to the last implementation.
You can think about this like a giant pattern matching.
The function expect to receive two arguments the current state of the machine and the action,
then it return a new state. -}
myBasket :: StateMachine States Actions
{-| First state we don't have items so we create the basket with one product -}
myBasket EmptyBasket (AddItem item) = return (ItemsInBasket $ createBasket item)
{-| Possible second state add more items-}
myBasket (ItemsInBasket items) (AddItem item) = return (ItemsInBasket $ addItem item items)
{-| Possible second state remove item from the basket-}
myBasket (ItemsInBasket items) (RemoveItem item) = return (ItemsInBasket $ removeItem item items)
{-| Possible second state to the checkout-}
myBasket (ItemsInBasket items) Checkout = return (NoPaymentSelected items)
{-| Third state select method of payment-}
myBasket (NoPaymentSelected items) (AddPayment payment) = return (PaymentSelected items payment)
{-| Last state confirm card and pay-}
myBasket (PaymentSelected items payment) Confirm = return (Check items (Total $ sumAllPrices 0 items) payment)
{-| Unhandled state-}
myBasket state _ = return state
-- | Utils functions
-- -------------------------------
createBasket :: Item -> [Item]
createBasket item = item : []
addItem :: Item -> [Item] -> [Item]
addItem item items = item : items
removeItem :: Item -> [Item] -> [Item]
removeItem item items = filter (\element -> desc element /= desc item) items
{-| Fold Function
---------------------
That make a recursive call to sum an element of the list and call the function again
with the new increase value and the list without that element that we just sum.
(item:items) with collection is a cool feature of Haskell where when you have a collection you receive
the item and the list of items without that item. Very handy for reduce.-}
sumAllPrices :: Double -> [Item] -> Double
sumAllPrices totalPrice (item:items) = sumAllPrices (totalPrice + (price item)) items
sumAllPrices totalPrice [] = totalPrice -- Last condition. When the list is empty we break the recursion
{-| Same function but using foldl and eta reduction-}
--sumAllPrices = foldl (\totalPrice item -> totalPrice + price item)
-- | State machine Types
-- -------------------------------
{-| Types for the shopping-}
data Item = Item
{ desc :: String
, price :: Double
} deriving (Show, Eq)
newtype Card =
Card String
deriving (Show, Eq)
newtype Money =
Money Double
deriving (Eq, Show)
newtype Total =
Total Double
deriving (Show, Eq)
data Basket =
Basket [Item]
Money
{-| State Types for the state machine-}
data States
= EmptyBasket
| ItemsInBasket [Item]
| NoPaymentSelected { items :: [Item] }
| PaymentSelected [Item]
(Either Card Money)
| Check [Item]
Total
(Either Card Money)
deriving (Show, Eq)
{-| Actions Types for the state machine, that make the machine change from one state to another-}
data Actions
= AddItem Item
| RemoveItem Item
| Checkout
| AddPayment (Either Card Money)
| Confirm
deriving (Show, Eq)
-- | Runner
-- -----------
goShoppingWithMoney :: IO ()
goShoppingWithMoney = do
state <- myBasket EmptyBasket $ AddItem (Item "Pepsi" 1.95)
print ("EmptyBasket: " ++ show state)
state <- myBasket state $ AddItem (Item "Donuts" 1.50)
print ("AddItem: " ++ show state)
state <- myBasket state $ AddItem (Item "Burgers" 4.50)
print ("AddItem: " ++ show state)
state <- myBasket state $ RemoveItem (Item "Donuts" 1.50)
print ("RemoveItem: " ++ show state)
state <- myBasket state $ AddItem (Item "Budbeiser pack" 6.00)
print ("AddItem: " ++ show state)
state <- myBasket state Checkout
print ("Checkout: " ++ show state)
state <- myBasket state $ AddPayment (Right $ Money $ sumAllPrices 0 (items state))
print ("AddPayment: " ++ show state)
state <- myBasket state Confirm
print ("Confirm: " ++ show state)
goShoppingWithCard :: IO ()
goShoppingWithCard = do
state <- myBasket EmptyBasket $ AddItem (Item "Coca-cola" 1.95)
print ("EmptyBasket: " ++ show state)
state <- myBasket state $ AddItem (Item "Twix" 1.50)
print ("AddItem: " ++ show state)
state <- myBasket state $ AddItem (Item "Pizza" 9.50)
print ("AddItem: " ++ show state)
state <- myBasket state $ RemoveItem (Item "Twix" 1.50)
print ("RemoveItem: " ++ show state)
state <- myBasket state Checkout
print ("Checkout: " ++ show state)
state <- myBasket state $ AddPayment (Left $ Card "1234-5678-9123-5678")
print ("AddPayment: " ++ show state)
state <- myBasket state Confirm
print ("Confirm: " ++ show state)