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