۲۵ - ۵ترانسفورمرِ 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 پیدا کنیم.