۲۲ - ۴نیوتایپِ State

‏‎State‎‏ هم مشابهِ ‏‎Reader‎‏ در فصلِ قبل، با ‏‎newtype‎‏ تعریف میشه:

newtype State s a =
  State { runState :: s -> (a, s) }

اول‌ش شاید عجیب باشه، ولی اگه دقت کنین متوجهِ یه شباهتی با ‏‎Reader‎‏ میشین:

newtype Reader r a =
  Reader { runReader :: r -> a }

نیوتایپ‌هایی که محتواشون تابع باشه زیاد دیدیم، بخصوص با نیوتایپ‌های ‏‎Monoid‎‏ (‏‎Sum‎‏، ‏‎Product‎‏، و غیره). نیوتایپ در پشت‌پرده باید با تایپی که می‌پوشونه یکسان باشه، چون در زمانِ کامپایل، نیوتایپ حذف میشه. پس تابعِ زیرِ نیوتایپ باید نسبت به تایپِ زیرین ایزومورفیک باشه. یعنی باید راهی باشه که بدونِ از دست دادنِ اطلاعات، از نیوتایپ به چیزی که می‌پوشونه بریم و برگردیم. برای مثال کُدِ زیر یه ایزومورفیسم رو نشون میده:

type Iso a b = (a -> b, b -> a)

newtype Sum a = Sum { getSum :: a }

sumIsIsomorphicWithItsContents
  :: Iso a (Sum a)
sumIsIsomorphicWithItsContents =
  (Sum, getSum)

ولی اینها نه:

-- ایزومورفیسم نیست، چون
-- .ممکنه کار نکنه
(a -> Maybe b, b -> Maybe a)

-- .به دو دلیل ایزومورفیسم نیست
-- بیشتر از یک [a] یکی اینکه وقتی
-- المان داشته باشه، اطلاعات از بین
-- [a] -> a میره. و دلیل دوم اینکه
-- ناقصه چون ممکنه هیچ المانی وجود
-- .نداشته باشه
[a] -> a, a -> [a]

حالا با توجه به اینها، برگردیم به داده‌ساز ِ ‏‎State‎‏ (برای گذاشتنِ یه مقدار داخل تایپِ ‏‎State‎‏) و دستگیره‌ی رکورد ِ ‏‎runState‎‏ (برای بیرون آوردنِ اون مقدار):

State :: (s -> (a, s)) -> State s a

runState :: State s a -> (a, s)

‏‎State‎‏ تابعی‌ه که حالت ِ ورودی رو می‌گیره و مقدار خروجی ‏‎a‎‏ رو به همراه حالت ِ جدید، در یه توپل برمی‌گردونه. نکته اینجاست که مقدارِ حالت ِ قبلی از هر اعمال، به حالت ِ بعدی زنجیر میشه، و این الگو کم پیش نمیاد. معمولاً از ‏‎State‎‏ برای چیزهایی مثل ژنراتورهای اعدادِ تصادفی، حل‌کننده‌ها، بازی‌ها، و حمل حافظه‌ی کاری در حین پیمایش ِ یه ساختار داده استفاده میشه.

برگردیم به اعدادِ تصادفی.

دقت کنین اینجا ‏‎random‎‏ چقدر شبیهِ ‏‎State‎‏ ِه:

random :: (Random a)
       => StdGen -> (a, StdGen)

State { runState
            :: s -> (a, s)    }

تابع ‏‎randomR‎‏ که فقط به یکی از آرگومان‌هاش اعمال شده باشه هم خیلی شبیهِ ‏‎State‎‏ ِه:

randomR :: (...) => (a, a) -> g -> (a, g)

State {    runState ::        s -> (a, s) }