Advertisement
Guest User

Untitled

a guest
Mar 18th, 2019
53
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.71 KB | None | 0 0
  1. {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  2. {-# LANGUAGE FlexibleContexts #-}
  3. {-# LANGUAGE FlexibleInstances #-}
  4. {-# LANGUAGE ScopedTypeVariables #-}
  5.  
  6. module ExceptionTest where
  7.  
  8. import Control.Concurrent
  9. import Control.Exception
  10. import Control.Monad
  11. import Control.Monad.Except
  12. import Control.Monad.Reader
  13. import Control.Monad.Trans.Class (lift)
  14. import Data.Typeable
  15. import qualified Data.Map.Strict as M
  16.  
  17.  
  18. data SaunaError = SaunaTooHot deriving Show
  19. data DbError = NoSuchUser deriving Show
  20.  
  21. data SpaErrors = SaunaErr SaunaError | DbErr DbError deriving (Show, Typeable)
  22. instance Exception SpaErrors
  23.  
  24. type Name = String
  25. type TempPreference = Int
  26. data User = User Name TempPreference deriving Show
  27.  
  28. type Database = M.Map Name User
  29.  
  30. newtype Connection = Connection (MVar Database)
  31. newtype TempController = TempController (MVar TempPreference)
  32.  
  33.  
  34. -------------------------
  35. ---- IO (Either a b) ----
  36. -------------------------
  37.  
  38. database :: Database
  39. database = M.fromList [ ("Joe", User "Joe" 65) , ("Fred", User "Fred" 101) ]
  40.  
  41. getUserFromDb :: Connection -> Name -> IO (Either SpaErrors User)
  42. getUserFromDb (Connection m) name = do
  43. db <- takeMVar m
  44. putMVar m db
  45. case M.lookup name db of
  46. Just user -> return $ Right user
  47. Nothing -> return . Left . DbErr $ NoSuchUser
  48.  
  49. setServerTemp :: TempController -> TempPreference -> IO ()
  50. setServerTemp (TempController m) newTemp = do
  51. _ <- takeMVar m
  52. putMVar m newTemp
  53.  
  54. checkServerTemp :: TempController -> IO (Either SpaErrors String)
  55. checkServerTemp (TempController m) = do
  56. tc <- takeMVar m
  57. putMVar m tc
  58. if tc > 100
  59. then return . Left . SaunaErr $ SaunaTooHot
  60. else return $ Right "New temp set"
  61.  
  62. main0 :: IO ()
  63. main0 = do
  64. db <- newMVar database
  65. tc <- newMVar 65
  66.  
  67. -- Our External Services:
  68. let dbConn = Connection db
  69. let tempController = TempController tc
  70.  
  71. user <- getUserFromDb dbConn "Joe"
  72. case user of
  73. Left err -> print err
  74. Right (User _ newTemp) -> do
  75. setServerTemp tempController newTemp
  76. res <- checkServerTemp tempController
  77. case res of
  78. Left err -> print err
  79. Right res -> print res -- irl this would call some other function
  80.  
  81. -- Using a Let statement to try to flatten the case statements:
  82. main1 :: IO ()
  83. main1 = do
  84. db <- newMVar database
  85. tc <- newMVar 65
  86.  
  87. -- Our External Services:
  88. let dbConn = Connection db
  89. let tempController = TempController tc
  90.  
  91. user <- getUserFromDb dbConn "Fred"
  92. let res :: Either SpaErrors TempPreference
  93. res = do
  94. User _ temp <- user
  95. -- You might want to do this:
  96. -- setServerTemp tempController temp :: IO (Either SaunaError String)
  97. -- but it doesn't typecheck because we are in an Either do block
  98. -- not an IO do block.
  99. return temp
  100. case res of
  101. Left err -> print err
  102. Right newTemp -> do
  103. setServerTemp tempController newTemp
  104. res' <- checkServerTemp tempController
  105. print res'
  106.  
  107.  
  108. --performCombinedIO :: Connection -> TempController -> Name -> ExceptT SpaErrors IO String
  109. --performCombinedIO conn tempCont user = do
  110. -- -- Notice how we got rid of the case tree
  111. -- User _ temp <- ExceptT $ getUserFromDb conn user
  112. -- -- setServerTemp is an IO action, but we are in ExceptT.
  113. -- -- So we must lift it:
  114. -- lift $ setServerTemp tempCont temp
  115. -- res <- ExceptT $ checkServerTemp tempCont
  116. -- return res
  117.  
  118. ------------------------
  119. ---- ExceptT a IO b ----
  120. ------------------------
  121.  
  122. getUserFromDb2 :: Connection -> Name -> ExceptT SpaErrors IO User
  123. getUserFromDb2 (Connection m) name = do
  124. db <- liftIO $ takeMVar m
  125. liftIO $ putMVar m db
  126. case M.lookup name db of
  127. Just user -> return user
  128. Nothing -> throwError $ DbErr NoSuchUser
  129.  
  130. checkServerTemp2 :: TempController -> ExceptT SpaErrors IO String
  131. checkServerTemp2 (TempController m) = do
  132. tc <- liftIO $ takeMVar m
  133. liftIO $ putMVar m tc
  134. if tc > 100
  135. then throwError $ SaunaErr SaunaTooHot
  136. else return "New temp set"
  137.  
  138. performCombinedIO2 :: Connection -> TempController -> Name -> ExceptT SpaErrors IO String
  139. performCombinedIO2 conn tempCont user = do
  140. -- Notice how we got rid of the case tree
  141. User _ temp <- getUserFromDb2 conn user
  142. -- setServerTemp is an IO action, but we are in ExceptT.
  143. -- So we must lift it:
  144. liftIO $ setServerTemp tempCont temp
  145. res <- checkServerTemp2 tempCont
  146. return res
  147.  
  148. main2 :: IO ()
  149. main2 = do
  150. db <- newMVar database
  151. tc <- newMVar 65
  152.  
  153. -- Our External Services:
  154. let dbConn = Connection db
  155. let tempCont = TempController tc
  156.  
  157. -- 3 examples to demonstrate the handling of the Lefts and Right
  158. res0 <- runExceptT $ performCombinedIO2 dbConn tempCont "Joe"
  159. res1 <- runExceptT $ performCombinedIO2 dbConn tempCont "Fred"
  160. res2 <- runExceptT $ performCombinedIO2 dbConn tempCont "unknown"
  161. print res0
  162. print res1
  163. print res2
  164.  
  165.  
  166. -------------------------------------
  167. ---- Adding ReaderT to the stack ----
  168. -------------------------------------
  169. -- Context (DB Connection and TempController) are avalaible
  170. -- everywhere we want it implicitely, however we end up doing
  171. -- lots of lifting. Also execution order in main is confusing.
  172.  
  173. data Env = Env { getDB :: Connection, getTC :: TempController }
  174.  
  175. -- Alternate Structure:
  176. getUserFromDb3' :: Connection -> Name -> ReaderT Env (ExceptT SpaErrors IO) User
  177. getUserFromDb3' = undefined
  178.  
  179. getUserFromDb3 :: Name -> ExceptT SpaErrors (ReaderT Env IO) User
  180. getUserFromDb3 name = do
  181. (Connection m) <- asks getDB
  182. db <- liftIO $ takeMVar m
  183. liftIO $ putMVar m db
  184. case M.lookup name db of
  185. Just user -> return user
  186. Nothing -> throwError $ DbErr NoSuchUser
  187.  
  188. setServerTemp' :: TempPreference -> ReaderT Env IO ()
  189. setServerTemp' newTemp = do
  190. TempController m <- asks getTC
  191. _ <- lift $ takeMVar m
  192. lift $ putMVar m newTemp
  193.  
  194. checkServerTemp3 :: ExceptT SpaErrors (ReaderT Env IO) String
  195. checkServerTemp3 = do
  196. TempController m <- asks getTC
  197. tc <- liftIO $ takeMVar m
  198. liftIO $ putMVar m tc
  199. if tc > 100
  200. then throwError $ SaunaErr SaunaTooHot
  201. else return "New temp set"
  202.  
  203.  
  204. performCombinedIO' :: Name -> ExceptT SpaErrors (ReaderT Env IO) String
  205. performCombinedIO' user = do
  206. User _ temp <- getUserFromDb3 user
  207. lift $ setServerTemp' temp
  208. res <- checkServerTemp3
  209. return res
  210.  
  211. main3 :: IO ()
  212. main3 = do
  213. db <- newMVar database
  214. tc <- newMVar 65
  215.  
  216. -- Our External Services:
  217. let dbConn = Connection db
  218. let tempCont = TempController tc
  219.  
  220. let env = Env dbConn tempCont
  221. -- 3 examples to demonstrate the handling of the Lefts and Right
  222. res1 <- flip runReaderT env $ runExceptT (performCombinedIO' "Joe")
  223. res2 <- flip runReaderT env $ runExceptT (performCombinedIO' "Fred")
  224. res3 <- flip runReaderT env $ runExceptT (performCombinedIO' "unknown")
  225. print res1
  226. print res2
  227. print res3
  228.  
  229.  
  230. --------------------------------------------
  231. ---- Wrapping The Stack In a Type Alias ----
  232. --------------------------------------------
  233. -- Simplified the execution in Main
  234.  
  235. type App a = ExceptT SpaErrors (ReaderT Env IO) a
  236.  
  237. runApp :: Env -> App a -> IO (Either SpaErrors a)
  238. runApp env = flip runReaderT env . runExceptT
  239.  
  240.  
  241. getUserFromDb4 :: Name -> App User
  242. getUserFromDb4 name = do
  243. (Connection m) <- asks getDB
  244. db <- liftIO $ takeMVar m
  245. liftIO $ putMVar m db
  246. case M.lookup name db of
  247. Just user -> return user
  248. Nothing -> throwError $ DbErr NoSuchUser
  249.  
  250.  
  251. checkServerTemp4 :: App String
  252. checkServerTemp4 = do
  253. TempController m <- asks getTC
  254. tc <- liftIO $ takeMVar m
  255. liftIO $ putMVar m tc
  256. if tc > 100
  257. then throwError $ SaunaErr SaunaTooHot
  258. else return "New temp set"
  259.  
  260. performCombinedIO4 :: Name -> App String
  261. performCombinedIO4 user = do
  262. User _ temp <- getUserFromDb4 user
  263. lift $ setServerTemp' temp
  264. res <- checkServerTemp4
  265. return res
  266.  
  267. main4 :: IO ()
  268. main4 = do
  269. db <- newMVar database
  270. tc <- newMVar 65
  271.  
  272. -- Our External Services:
  273. let dbConn = Connection db
  274. let tempCont = TempController tc
  275. let env = Env dbConn tempCont
  276.  
  277. res1 <- runApp env (performCombinedIO4 "Joe")
  278. res2 <- runApp env (performCombinedIO4 "Fred")
  279. res3 <- runApp env (performCombinedIO4 "unknown")
  280. print res1
  281. print res2
  282. print res3
  283.  
  284.  
  285. ----------------------------
  286. ---- MTL Style: Round 1 ----
  287. ----------------------------
  288.  
  289. getUserFromDb5 ::
  290. ( MonadReader Env m
  291. , MonadIO m
  292. , MonadError SpaErrors m
  293. ) => Name -> m User
  294. getUserFromDb5 name = do
  295. (Connection m) <- asks getDB
  296. db <- liftIO $ takeMVar m
  297. liftIO $ putMVar m db
  298. case M.lookup name db of
  299. Just user -> return user
  300. Nothing -> throwError . DbErr $ NoSuchUser
  301.  
  302. setServerTemp5 ::
  303. ( MonadReader Env m
  304. , MonadIO m
  305. ) => TempPreference -> m ()
  306. setServerTemp5 newTemp = do
  307. TempController m <- asks getTC
  308. _ <- liftIO $ takeMVar m
  309. liftIO $ putMVar m newTemp
  310.  
  311. checkServerTemp5 ::
  312. ( MonadReader Env m
  313. , MonadIO m
  314. , MonadError SpaErrors m
  315. ) => m String
  316. checkServerTemp5 = do
  317. TempController m <- asks getTC
  318. tc <- liftIO $ takeMVar m
  319. liftIO $ putMVar m tc
  320. if tc > 100
  321. then throwError . SaunaErr $ SaunaTooHot
  322. else return "New temp set"
  323.  
  324. performCombinedIO5 ::
  325. ( MonadReader Env m
  326. , MonadIO m
  327. , MonadError SpaErrors m
  328. ) => Name -> m String
  329. performCombinedIO5 user = do
  330. User _ temp <- getUserFromDb5 user
  331. setServerTemp5 temp
  332. res <- checkServerTemp5
  333. return res
  334.  
  335. main5 :: IO ()
  336. main5 = do
  337. db <- newMVar database
  338. tc <- newMVar 65
  339.  
  340. -- Our External Services:
  341. let dbConn = Connection db
  342. let tempCont = TempController tc
  343.  
  344. let env = Env dbConn tempCont
  345. -- 3 examples to demonstrate the handling of the Lefts and Right
  346. res1 <- flip runReaderT env $ runExceptT (performCombinedIO5 "Joe")
  347. res2 <- flip runReaderT env $ runExceptT (performCombinedIO5 "Fred")
  348. res3 <- flip runReaderT env $ runExceptT (performCombinedIO5 "unknown")
  349. print res1
  350. print res2
  351. print res3
  352.  
  353.  
  354. --------------------------------------
  355. ---- MTL Style Round2: HasPattern ----
  356. --------------------------------------
  357.  
  358. class HasTempController a where
  359. getTempControl :: a -> TempController
  360. instance HasTempController Env where
  361. getTempControl = getTC
  362. instance HasTempController TempController where
  363. getTempControl = id
  364.  
  365. class HasDatabase a where
  366. getDatabase :: a -> Connection
  367. instance HasDatabase Env where
  368. getDatabase = getDB
  369. instance HasDatabase Connection where
  370. getDatabase = id
  371.  
  372. getUserFromDb6 ::
  373. ( MonadReader Env m
  374. , MonadIO m
  375. , MonadError SpaErrors m
  376. ) => Name -> m User
  377. getUserFromDb6 name = do
  378. (Connection m) <- asks getDatabase
  379. db <- liftIO $ takeMVar m
  380. liftIO $ putMVar m db
  381. case M.lookup name db of
  382. Just user -> return user
  383. Nothing -> throwError . DbErr $ NoSuchUser
  384.  
  385. setServerTemp6 ::
  386. ( MonadReader Env m
  387. , MonadIO m
  388. ) => TempPreference -> m ()
  389. setServerTemp6 newTemp = do
  390. TempController m <- asks getTempControl
  391. _ <- liftIO $ takeMVar m
  392. liftIO $ putMVar m newTemp
  393.  
  394. checkServerTemp6 ::
  395. ( MonadReader Env m
  396. , MonadIO m
  397. , MonadError SpaErrors m
  398. ) => m String
  399. checkServerTemp6 = do
  400. TempController m <- asks getTempControl
  401. tc <- liftIO $ takeMVar m
  402. liftIO $ putMVar m tc
  403. if tc > 100
  404. then throwError . SaunaErr $ SaunaTooHot
  405. else return "New temp set"
  406.  
  407. performCombinedIO6 ::
  408. ( MonadReader Env m
  409. , MonadIO m
  410. , MonadError SpaErrors m
  411. ) => Name -> m String
  412. performCombinedIO6 user = do
  413. User _ temp <- getUserFromDb5 user
  414. setServerTemp5 temp
  415. res <- checkServerTemp5
  416. return res
  417.  
  418. main6 :: IO ()
  419. main6 = do
  420. db <- newMVar database
  421. tc <- newMVar 65
  422.  
  423. -- Our External Services:
  424. let dbConn = Connection db
  425. let tempCont = TempController tc
  426.  
  427. let env = Env dbConn tempCont
  428. -- 3 examples to demonstrate the handling of the Lefts and Right
  429. res1 <- flip runReaderT env $ runExceptT (performCombinedIO6 "Joe")
  430. res2 <- flip runReaderT env $ runExceptT (performCombinedIO6 "Fred")
  431. res3 <- flip runReaderT env $ runExceptT (performCombinedIO6 "unknown")
  432. print res1
  433. print res2
  434. print res3
  435.  
  436.  
  437. ----------------------------------
  438. ---- MTL Style Round3: Purity ----
  439. ----------------------------------
  440.  
  441. newtype AppM a = AppM { unAppM :: ExceptT SpaErrors (ReaderT Env IO) a}
  442. deriving (Functor, Applicative, Monad, MonadIO, MonadReader Env, MonadError SpaErrors)
  443.  
  444. runAppM :: Env -> AppM a -> IO (Either SpaErrors a)
  445. runAppM env = flip runReaderT env . runExceptT . unAppM
  446.  
  447. class Monad m => MonadDatabase m where
  448. readDatabase :: m Database
  449. instance (MonadIO m, MonadReader Env m) => MonadDatabase (ReaderT Env m) where
  450. readDatabase = do
  451. Connection m <- asks getDatabase
  452. db <- liftIO $ takeMVar m
  453. liftIO $ putMVar m db
  454. return db
  455.  
  456. class Monad m => MonadTemp m where
  457. setTemp :: TempPreference -> m ()
  458. checkTemp :: m TempPreference
  459. instance (MonadIO m, MonadReader Env m) => MonadTemp (ReaderT Env m) where
  460. setTemp newTemp = do
  461. TempController m <- asks getTempControl
  462. _ <- liftIO $ takeMVar m
  463. liftIO $ putMVar m newTemp
  464. return ()
  465. checkTemp = do
  466. TempController m <- asks getTempControl
  467. temp <- liftIO $ takeMVar m
  468. liftIO $ putMVar m temp
  469. return temp
  470.  
  471. -- This function can no longer do arbitary IO or asks
  472. getUserFromDb7 ::
  473. ( MonadError SpaErrors m
  474. , MonadDatabase m
  475. ) => Name -> m User
  476. getUserFromDb7 name = do
  477. db <- readDatabase
  478. case M.lookup name db of
  479. Just user -> return user
  480. Nothing -> throwError . DbErr $ NoSuchUser
  481.  
  482. -- Kinda silly function now that its all been moved to the class instance.
  483. -- However, the point is that all the specific sideeffects have been enumerated
  484. -- and GHC wont let this function do anything other then what is specified by
  485. -- MonadTemp.
  486. setServerTemp7 ::
  487. MonadTemp m => TempPreference -> m ()
  488. setServerTemp7 = setTemp
  489.  
  490. checkServerTemp7 ::
  491. ( MonadError SpaErrors m
  492. , MonadTemp m
  493. ) => m String
  494. checkServerTemp7 = do
  495. tc <- checkTemp
  496. if tc > 100
  497. then throwError . SaunaErr $ SaunaTooHot
  498. else return "New temp set"
  499.  
  500. -- This function can do anything baked into AppM:
  501. performCombinedIO7 :: Name -> AppM String
  502. performCombinedIO7 user = do
  503. User _ temp <- getUserFromDb5 user
  504. setServerTemp5 temp
  505. checkServerTemp5
  506.  
  507. main7 :: IO ()
  508. main7 = do
  509. db <- newMVar database
  510. tc <- newMVar 75
  511.  
  512. -- Our External Services:
  513. let dbConn = Connection db
  514. let tempCont = TempController tc
  515.  
  516. let env = Env dbConn tempCont
  517. -- 3 examples to demonstrate the handling of the Lefts and Right
  518. res1 <- runAppM env (performCombinedIO7 "Joe")
  519. res2 <- runAppM env (performCombinedIO7 "Fred")
  520. res3 <- runAppM env (performCombinedIO7 "unknown")
  521. print res1
  522. print res2
  523. print res3
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement