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