۲۲ - ۴نیوتایپِ 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) }