۲۵ - ۵ترانسفورمرِ State یا StateT
مشابه Reader
و ReaderT
، StateT
هم همون State
ِه که فقط جوابش یه ساختار ِ اضافهی موندی هم داره. StateT
یه ذره از State Monad
که قبلاً دیدیم بیشتر کاربرد داره. مثلِ ReaderT
، مقدارِ StateT
هم یه تابعه:
newtype StateT s m a =
StateT { runStateT :: s -> m (a, s) }
تمرینها: StateT
اگه با تفاوتشون آشنا هستین، اینجا قراره اکید بنویسین. برای اکید بودن لازم نیست کارِ خاصی انجام بدین. بدیهیترین چیزی که کار میکنه رو بنویسین. اگه بخواین تنبل بنویسین (دقیقتر بگیم، تنبلتر) یه چیزایی باید اضافه کنین. تفاوتشون رو در فصلِ مرتبط با نااکیدی توضیح میدیم.
۱.
اول باید نمونههای Functor
و Applicative
رو بنویسین، چون از قبل برای تایپِ Monad m => s -> m (a, s)
نمونههای Functor
و Applicative
تعریف نشدن.
instance (Functor m)
=> Functor (StateT s m) where
fmap f m = undefined
۲.
مثلِ Functor
، نمونه ِ Applicative
هم خودتون باید بنویسین. یعنی نمیشه از یه نمونه ِ Applicative
که قبلاً تعریف شده استفاده کنین، خودتون باید با تایپِ s -> m (a, s)
کار کنین.
instance (Monad m)
=> Applicative (StateT s m) where
pure = undefined
(<*>) = undefined
توجه هم داشته باشین که برخلافِ انتظار، محدودیت ِ روی m
، Applicative
نیست، بلکه Monad
ِه. دلیلش اینه که بدونِ یه Monad
برای m
، نمیشه محاسباتِ ترتیبداری که از StateT Applicative
انتظار میره رو بیان کرد. برای اطلاعاتِ بیشتر، این سؤال در Stack Overflow* رو نگاه کنین. این مشکل ِ Github در مخزن ِ کورسِ NICTA هم نگاه کنین. ولی مراقب باشین! کورسِ NICTA جوابِ این تمرین رو لو میده. مشکلِ اصلی اینجاست که بدونِ Monad
، بجای زنجیر کردن محاسباتِ StateT
، هر بار مقدار اولیه رو پاس میدین. این یه الگو ِ جامع از تمایزِ Applicative
و Monad
ِه و ارزش داره روش بیشتر وقت بذارین.
instance (Monad m)
=> Monad (StateT s m) where
return = pure
sma >>= f = undefined
عنوان سؤال: آیا میشه این رو تعریف کرد؟ (Applicative m) => Applicative (StateT s m)
ReaderT
، WriterT
، StateT
میخوایم به یه چیزی در موردِ این سهتا تایپ اشاره کنیم:
newtype Reader r a =
Reader { runReader :: r -> a }
newtype Writer w a =
Writer { runWriter :: (a, w) }
newtype State s a =
State { runState :: s -> (a, s) }
و نسخههای ترانسفورمرِشون:
newtype ReaderT r m a =
ReaderT { runReaderT :: r -> m a }
newtype Writer m w a =
WriterT { runWriterT :: m (a, w) }
newtype StateT s m a =
StateT { runStateT :: s -> m (a, s) }
با Reader
و State
آشنا هستین. تا اینجا Writer
یا WriterT
رو اصلاً معرفی نکردیم، چون رک بگیم، نباید ازشون استفاده کنین. دلیلش رو جلوتر در این فصل توضیح میدیم.
به طورِ خلاصه، Applicative
و Monad
ِ Writer
مقادیرِ w
رو به صورتِ مانویدی با هم ترکیب میکنن. این توصیف نشون میده که با Reader
میشه با مقادیری که لازم داریم کار کنیم، با Writer
میشه با مقادیری که میتونیم "ساتع" و ترکیب کنیم (اما نخونیم) کار کنیم، و با State
میشه به هر نحوی که میخوایم (که مانویدی هم شامل میشه) هم مقادیر رو بخونیم و هم بنویسیم. این یکی از دلایلیه که نیازی به Writer
نیست، چون در هر حال State
میتونه جایگزینش باشه. بعداً دلایلش رو بیشتر توضیح میدیم.
در واقع یه تایپ در کتابخونه ِ transformers
هست که Reader
، Writer
، و State
رو به یک تایپِ بزرگ ترکیب میکنه:
newtype RWST r w s m a =
RWST { runRWST :: r -> s -> m (a, s, w) }
به خاطر وجودِ اون Writer
داخلش، استفاده از این تایپ هم توصیه نمیشه، اما خوبه که بدونین وجود داره.
ارتباطِ بین StateT
و Parser
احتمالاً به خاطر دارین که تایپِ یه پارسر ِ ساده چه شکلیه:
type Parser a = String -> Maybe (a, String)
شاید صحبتهایی که در فصلِ پارسرها راجع به شباهتهای بین پارسر و State
کردیم هم یادتون باشه. اگه بخوایم میتونیم یه تایپِ Parser
رو اینطوری تعریف کنیم:
newtype StateT s m a =
StateT { runStateT :: s -> m (a, s) }
type Parser = StateT String Maybe
هیچکس در عمل این کار رو نمیکنه، اما دیدنِ تشابهِ بین این دوتا کمک میکنه درکِ بهتری از StateT
پیدا کنیم.