۲۸ - ۲بیراهه‌هایی که توضیحاتِ IO میرَن

خیلی از توضیحاتِ ‏‎IO‎‏ غلط‌انداز یا سردرگم‌اند. چندتا مثال از اون توضیح‌ها رو دیدیم. بعضی‌ها هم بهش میگن "‏‎IO Monad‎‏" و ظاهراً ‏‎IO‎‏ رو با ‏‎Monad‎‏ یکی میدونن. درسته که ‏‎IO‎‏ تایپی‌ه که یه نمونه ِ ‏‎Monad‎‏ داره، ولی فقط یه ‏‎Monad‎‏ نیست و موندها هم فقط ‏‎IO‎‏ نیستن. بعضی دیگه از توضیح‌ها طوری القا می‌کنن که با ورود به ‏‎IO‎‏ دیگه خلوص و شفافیتِ مرجع نابود میشن. بعضی‌ها هم اصلاً چیزی از ‏‎IO‎‏ نمیگن چون در حقیقت، با مبهم موندنِ ‏‎IO‎‏ هم میشه خیلی از کارها رو انجام داد.

می‌خوایم چندتا راهنمایی برای سنجشِ منتقدانه‌ی توضیحاتِ ‏‎IO‎‏ بگیم. اول بریم سراغ یکی از شناخته‌شده‌ترین توضیحاتی که برای ‏‎IO‎‏ داده شده. این توضیح سعی کرده ‏‎IO‎‏ رو براساس ‏‎State‎‏ توضیح بده.

‏‎State‎‏ رو آتیش بزن!

استفاده از ‏‎State‎‏ برای فهموندنِ ‏‎IO‎‏ خیلی وسوسه‌برانگیزه. یه نگاه به این جمله که از اوایلِ مستندات ِ ‏‎GHC.IO‎‏ گرفته شده بندازین:

‏‎IO Monad‎‏ فقط یه نمونه از موند ِ ‏‎ST‎‏ ِه، که حالت ِش دنیای واقعی ِه.

وقتی تایپ‌های پشت‌پرده رو نگاه کنین، دلیلِ چنین توضیحاتی رو راحت میشه فهمید:

-- ghc-prim از
import GHC.Prim
import GHC.Types

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

-- :info IO
newtype IO a =
  IO (State# RealWorld
      -> (# State# RealWorld, a #))

قبوله، واقعاً شبیهِ ‏‎State‎‏ ِه! با همه‌ی اینها، اون قدری که اول به نظر میرسه فایده نداره.

مشکلِ این توضیح اینه که در واقع اون ‏‎State#‎‏* ِ پشت‌پرده‌ی ‏‎IO‎‏، نه دیده میشه نه میشه باهاش تعامل داشت. ‏‎State#‎‏، به اون مفهومی که از ‏‎State‎‏، ‏‎StateT‎‏، یا حتی ‏‎ST‎‏ استفاده میشه، ‏‎State‎‏ نیست، ولی مطمئناً اینجا رفتارِ ‏‎s‎‏ خیلی شبیهِ رفتارِ ‏‎ST‎‏ ِه.

*

علامتِ ‏‎#‎‏ نشون دهنده‌ی یه تایپِ بَدوی ِه. این تایپ‌ها رو توی خودِ هسکل نمیشه تعریف کرد و از ماژول ِ ‏‎GHC.Prim‎‏ صادر میشن.

اینجا نقشِ ‏‎State‎‏ اینه که ترتیبِ اجراییه‌های ‏‎IO‎‏ رو به GHC بگه، و مشخص کنه چه اجراییه ِ ‏‎IO‎‏‌یی یکتاست. اگه باز هم به مستندات ِ ‏‎GHC.Prim‎‏ نگاه کنیم:

‏‎State#‎‏ تایپِ بَدَوی و لیفت‌نشده ِ حالت‌هاست. یک پارامترِ تایپی داره، پس می‌تونه ‏‎State# RealWorld‎‏ یا ‏‎State# s‎‏ باشه، که ‏‎s‎‏ متغیر تایپی ِه. تنها نقشِ پارامترِ تایپی اینه که ریسه‌های حالتِ مختلف رو از هم سوا نگه داره. با هیچ چیزی ارائه نمیشه.

‏‎RealWorld‎‏ عمیقاً جادویی‌ه. بَدِوی ِه، اما لیفت‌نشده نیست (در نتیجه ‏‎ptrArg‎‏). هیچ وقت با مقادیرِ تایپِ ‏‎RealWorld‎‏ کار نمی‌کنیم؛ فقط در تایپ سیستم برای پارامتردار کردنِ ‏‎State#‎‏ به کار میره.

وقتی میگن ‏‎RealWorld‎‏ "با هیچ چیزی ارائه نمیشه،" منظورشون اینه که واقعاً صفر بیت حافظه مصرف می‌کنه. نشانه‌های حالت که در زیرِ تایپِ ‏‎IO‎‏ هستن در زمانِ کامپایل پاک میشن و هیچ سرباری ای به زمانِ اجرا اضافه نمی‌کنن. پس در واقع مشکلِ توضیح دادنِ ‏‎IO‎‏ براساسِ ‏‎State‎‏ این نیست که غلطه؛ مشکل اینجاست که ‏‎State‎‏ ای نیست که بشه تعاملِ مفهوم‌داری، مشابهِ بقیه‌ی تایپ‌های ‏‎State‎‏ باهاش داشت.