۲۵ - ۱۱موند ترانسفورمرها در عمل
MaybeT در عمل
اینها چندتا مثال از MaybeT در عمل هستن؛ ما توضیحی براشون نمیدیم و فرصت میدیم خودتون اگه خواستین بیشتر تحقیق کنین. منبعِ کُدها رو کامنت کردیم.
-- github.com/wavewave/hoodle-core
recentFolderHook
:: MainCoroutine (Maybe FilePath)
recentFolderHook = do
xstate <- get
(r :: Maybe FilePath) <- runMaybeT $ do
hset <- hoist (view hookset xstate)
rfolder <-
hoist (H.recentFolderHook hset)
liftIO rfolder
return r
-- github.com/devalot/hs-exceptions
-- src/maybe.hs
addT :: FilePath
-> FilePath
-> IO (Maybe Integer)
addT f1 f2 = runMaybeT $ do
s1 <- sizeT f1
s2 <- sizeT f2
return (s1 + s2)
-- wavewave/ghcjs-dom-delegator
-- example/Example.hs
main :: IO ()
main = do
clickbarref <-
asyncCallback1 AlwaysRetain clickbar
clickbazref <-
asyncCallback1 AlwaysRetain clickbaz
r <- runMaybeT $ do
doc <- MaybeT currentDocument
bar <- lift . toJSRef
=<< MaybeT
(documentQuerySelector doc
(".bar" :: JSString))
baz <- lift . toJSRef
=<< MaybeT
(documentQuerySelector doc
(".baz" :: JSString))
lift $ do
ref <- newObj
del <- delegator ref
addEvent bar "click" clickbarref
addEvent baz "click" clickbazref
case r of
Nothing -> print "something wrong"
Just _ -> print "welldone"توسعهی موقتِ ساختار
با اینکه عموماً موند ترانسفورمرها برای تعریفِ یه بافت ِ بزرگ برای یه برنامه شناخته میشن (بخصوص با چیزهایی مثل ReaderT) راههای دیگهای هم وجود دارن. یکی از الگوهای مفید، توسعهی موقتِ ساختار ِ اضافهست که جلوی تکرارنوشت رو میگیره. یه مثال از چنین الگویی با استفاده از Maybe و scotty:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad.IO.Class
import Data.Maybe (fromMaybe)
import Data.Text.Lazy (Text)
import Web.Scotty
param' :: Parsable a
=> Text -> ActionM (Maybe a)
param' k = rescue (Just <$> param k)
(const (return Nothing))
main = scotty 3000 $ do
get "/:word" $ do
beam' <- param' "word"
let beam = fromMaybe "" beam'
i <- param' "num"
liftIO $ print (i :: Maybe Integer)
html $
mconcat ["<h1>Scotty, ",
beam,
" me up!</h1>"]این کار میکنه، اما اگه چندتا چیز داشتیم که ActionM (Maybe...) خروجی میدادن و میخواستیم به ازای شکست ِ یکیشون همهشون شکست بخورن، زود پیچیده میشد. حالا با MaybeT یه کارِ مشابه میکنیم:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Maybe
import Data.Maybe (fromMaybe)
import Data.Text.Lazy (Text)
import Web.Scotty
param' :: Parsable a
=> Text -> MaybeT ActionM a
param' k = MaybeT $
rescue (Just <$> param k)
(const (return Nothing))
type Reco =
(Integer, Integer, Integer, Integer)
main = scotty 3000 $ do
get "/:word" $ do
beam <- param "word"
reco <- runMaybeT $ do
a <- param' "1"
liftIO $ print a
b <- param' "2"
c <- param' "3"
d <- param' "4"
(lift . lift) $ print b
return ((a, b, c, d) :: Reco)
liftIO $ print reco
html $
mconcat ["<h1>Scotty, ",
beam,
" me up!</h1>"]اینجا چندتا نکتهی مهم هست:
۱.
حتی با وجود ساختار ِ اضافه، لازم بود فقط یکبار از liftIO استفاده کنیم، در حالی که با lift باید دوبار لیفت میکردیم تا به MaybeT و ActionT اشاره کنیم.
۲.
به خاطر اون بایند ِ گُندهی MaybeT، میشه وجود داشتنِ a، b، c، و d رو فرض کنیم، اما مقدارِ reco خودش Maybe Reco ِه، چون هر بخش از محاسبه ممکنه در صورتِ نبودِ پارامترِ مورد نیازش شکست بخوره.
۳.
به خاطرِ اون runMaybeT جلوی do، میدونه برای بلوک ِ do چه موندی منظورمون بوده. علاوه بر این، مقدارِ MaybeT هم باز میکنه و مقدارِ ActionM (Maybe Reco) رو از توش در میاره، که از طریقِ بایند به Maybe Reco میرسیم.
ExceptT، یا همون EitherT در عمل
مثالی که با scotty و Maybe زدیم خیلی راضیکننده نیست، چون در صورتِ شکست، تنها اطلاعاتی که به کاربر میده Nothing ِه. کلاً باید تکلیفِ Maybe رو زود مشخص کرد (معمولاً همون جایی که درست میشه) تا جلوی مقادیرِ Nothing ای که معلوم نیست از کجا پیداشون شده گرفته بشه. ارائهی مقادیرِ Nothing به کاربرِ نهایی هم کار خوبی نیست. خوشبختانه برای اتصال-کوتاههای گویاتر (شکستهای گویاتر)، Either رو داریم!
باز هم scotty
برای نشون دادنِ این مورد دوباره از scotty استفاده میکنیم. یه بار دیگه مثالِ ساده میزنیم:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad.IO.Class
import Data.Text.Lazy (Text)
import Web.Scotty
param' :: Parsable a
=> Text -> ActionM (Either String a)
param' k =
rescue (Right <$> param k)
(const
(return (Left $ "The key: "
++ show k
++ " was missing!")))
main = scotty 3000 $ do
get "/:word" $ do
beam <- param "word"
a <- param' "1"
let a' = either (const 0) id a
liftIO $ print (a :: Either String Int)
liftIO $ print (a' :: Int)
html $
mconcat ["<h1>Scotty, ",
beam,
" me up!</h1>"]دقت کنین که برای دسترسی به مقدارِ Int، باید Either رو دستی فولد میکردیم. ولی در کُدِ واقعی سعی کنین از اینطور مقادیرِ پیشفرض اجتناب کنین. اگه تعداد زیاد بشه، زود ناجور میشه.
حالا همون کار رو این بار با ExceptT از transformers انجام بدیم. به خاطر داشته باشین که ExceptT یه اسمِ دیگه برای EitherT ِه:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as TL
import Web.Scotty
param' :: Parsable a
=> Text -> ExceptT String ActionM a
param' k =
ExceptT $
rescue (Right <$> param k)
(const
(return
(Left $ "The key: "
++ show k
++ " was missing!")))
type Reco =
(Integer, Integer, Integer, Integer)
tshow = TL.pack . show
main = scotty 3000 $ do
get "/" $ do
reco <- runExceptT $ do
a <- param' "1"
liftIO $ print a
b <- param' "2"
c <- param' "3"
d <- param' "4"
(lift . lift) $ print b
return ((a, b, c, d) :: Reco)
case reco of
(Left e) -> test (TL.pack e)
(Right r) ->
html $
mconcat ["<h1>Success! Reco was: ",
tshow r,
"</h1>"]اگه یه درخواست مثلِ این بهش بدین:
http://localhost:3000/?1=1دنبال ۲ میگرده، چون بعد از ۱، پارامتر ۲ رو خواستین.
اگه یه درخواست مثلِ این بهش بدین:
http://localhost:3000/?1=1&2=2&3=3&4=4باید چنین پاسخی ببینین:
Success! Reco was: (1,2,3,4)یه بار دیگه از یه بایند ِ گُنده (زیرِ ExceptT) بهره بردیم.
کدِ یه ذره پیشرفته
ممکنه در چنین فضایی EitherT کاربرد داشته باشه:
type Et a = Either SDLErr IO a
mkWindow :: HasSDLErr m =>
String
-> CInt -> CInt
-> m SDL.Window
mkRenderer :: HasSDLErr m
=> SDL.Window -> m SDL.Renderer
hasSDLErr :: (MonadIO m, MonadError e m)
=> (a -> b)
-> (a -> Bool)
-> e -> IO a -> m b
hasSDLErr g f e a =
liftIO a
>>= \r ->
bool (return $ g r)
(throwError e) $ f r
class (MonadIO m, MonadError SDLErr m)
=> HasSDLErr m where
decide :: (a -> Bool)
-> SDLErr -> IO a -> m a
decide' :: (Eq n, Num n)
=> SDLErr -> IO n -> m ()
instance HasSDLErr
(EitherT SDLErr IO) where
decide = hasSDLErr id
decide' = hasSDLErr (const ()) (/= 0)و در عمل:
initialise :: Et (SDL.Window,SDL.Renderer)
initialise = do
initSDL [SDL.SDL_INIT_VIDEO]
win <-
mkWindow "Meteor!"
screenHeight
screenWidth
rdr <- mkRenderer win
return (win,rdr)
createMeteor :: IO (Either SDLErr MeteorS)
createMeteor = do
eM <- runEitherT initialise
retrun $ mkMeteor <$> eM
where
emptyBullets = V.empty
mkMeteor (w,r) = MeteorS w r
getInitialPlayer
emptyBullets
getInitialMobs
False