۶ - ۱۰تایپکلاس Show

این تایپکلاس راهی برای ارائه‌ی داده‌ها با نوشتاری که برای آدم‌ها خوانا باشه در اختیار میذاره. GHCi از ‏‎Show‎‏ برای ساخت مقادیر ‏‎String‎‏ و چاپ ِ اونها تو ترمینال استفاده می‌کنه.

‏‎Show‎‏ یه فرمت برای سریال‌سازی نیست. سریال‌سازی برای تبدیل داده‌ها به فرمت نوشتاری یا باینری به منظور ماندگاری یا مکاتبه با بقیه‌ی کامپیوترهای روی شبکه به کار میره. یک مثال از ماندگاری، ذخیره‌سازیِ داده روی دیسک‌ه. ‏‎Show‎‏ برای هیچ کدوم از این کارها مناسب نیست؛ فقط برای خوانایی استفاده میشه.

اطلاعات تایپکلاس از این قرارند (خلاصه شده):

class Show a where
  showsPrec :: Int -> a -> ShowS
  show :: a -> String
  showList :: [a] -> ShowS

instance Show a => Show [a]
instance Show Ordering
instance Show a => Show (Maybe a)
instance Show Integer
instance Show Int
instance Show Char
instance Show Bool
instance Show ()
instance Show Float
instance Show Double

انواع تایپ‌های عددی، مقادیر ‏‎Bool‎‏، توپل‌ها، و حروف تویِ اون لیست هستن. یعنی یه قابلیت تعریف شده دارن تا روی صفحه چاپ بشن. یه تابعِ ‏‎show‎‏ هم هست که یه ورودیِ پلی‌مورفیک می‌گیره و به عنوانِ یه ‏‎String‎‏ برمی‌گردونه تا بشه چاپ‌ِش کرد.

چاپ کردن و عوارضِ جانبی

هر وقت جواب یه بیانیه رو از GHCi می‌خواین و روی صفحه چاپ میشه، غیرمستقیم تابعی به اسم ‏‎print‎‏ رو صدا می‌زنین. این تابع رو خیلی مختصر تو فصلِ نوشته‌ها دیدیم، سَرِ تابعِ ‏‎max‎‏ هم در بخشِ ‏‎Ord‎‏ وقتی پیغام خطا (به خاطر آرگومان کم) گرفتیم از ‏‎print‎‏ صحبت کردیم. از اونجا که درک این تابع در درک تایپکلاسِ ‏‎Show‎‏ خیلی مهمه، یه گریزی می‌زنیم و با جزئیات بیشتر راجع بهش صحبت می‌کنیم.

هسکل یه زبان برنامه‌نویسی تابعی خالصه. تابعی به خاطر اینکه برنامه‌ها در واقع به عنوان توابع نوشته میشن، مشابه معادلات ریاضی که یه عملیات به چندتا آرگومان اعمال میشه تا خروجی بده. هسکل رو به این دلیل خالص توصیف می‌کنیم چون هر بیانیه‌ی اون رو میشه با جبر لاندا هم بیان کرد.

شاید واضح به نظر نرسه که چرا چاپ ِ نتایج روی صفحه ممکنه موجب نگرانی باشه. تابع علاوه بر اعمال به آرگومان‌هایی که در گستره ِش هستن می‌خواد به نحوی به دنیای بیرون از گستره ِش هم اثر بذاره، که این اثرگذاری، همون نشون دادنِ نتیجه‌ش روی صفحه‌ست. به همین نتیجه‌ی قابلِ ملاحظه جدا از جواب بیانیه، یک عارضه یا اثرِ جانبی میگیم. هسکل برای مدیریتِ اثرات، محاسبات عارضه‌دار رو طوری از محاسبات خالص جدا می‌کنه که امنیت و قابل پیش‌بینی بودنِ حسابِ توابع حفظ بشه. این کار باعث میشه خودِ محاسبات اثردار هم، ترکیب‌پذیر‌تر و راحت‌تر قابل پیگیری و درک بشن. از مزایای اینطور جدا کردنِ اثرات، درک و پیش‌بینی راحت‌ترِ نتایج توابع‌ه.

هسکل بدون اینکه چیزی به جبر لاندا ِ خالص (که هسته‌ی ساختاری‌شه) اضافه کنه، راهی برای نوشتنِ برنامه‌های عادی که با دنیای بیرون صحبت می‌کنند ارائه میده، و همین موضوع هسکل رو از بقیه‌ی زبان‌های برنامه‌نویسی تابعیِ دیگه متمایز می‌کنه. همین خاصیت – که فقط و فقط جبر لاندا هست و نه چیز دیگه – هسکل رو یه زبان برنامه‌نویسی تابعیِ خالص می‌کنه.

GHCi هرازگاهی تابع ‏‎print‎‏ رو به طور غیرمستقیم صدا میزنه، اما تایپ‌ش صراحتاً اثردار بودن رو نشون میده. تا اینجا طرز کارش رو توضیح ندادیم، ولی حالا وقتشه یه مقدار عمیق‌تر نگاه‌ش کنیم.

‏‎print‎‏ در ‏‎Prelude‎‏ ِ استاندارد تعریف شده، تابعی که "یه مقدار از هر تایپِ قابلِ چاپ رو به دستگاهِ استانداردِ خروجی میده. تایپ‌های قابل چاپ، تایپ‌هایی‌اند که نمونه‌هایی از کلاسِ ‏‎Show‎‏ هستن؛ ‏‎print‎‏ مقادیر رو با ‏‎show‎‏ به نوشته برای خروجی تبدیل می‌کنه، و بعد یه خطِ‌جدید اضافه می‌کنه." تایپ تابع ‏‎print‎‏ رو ببینیم:

Prelude> :t print
print :: Show a => a -> IO ()

می‌بینیم که ‏‎print‎‏ یه آرگومان، ملزوم به داشتنِ یه نمونه از ‏‎Show‎‏ می‌گیره و یه خروجیِ ‏‎IO ()‎‏ برمی‌گردونه. این خروجی یک اجراییه ِ ‏‎IO‎‏ ِه که یه مقدار با تایپِ ‏‎()‎‏ برمی‌گردونه.

این جوابِ ‏‎IO ()‎‏ رو قبلاً وقتی از چاپ ِ نوشته‌ها صحبت کردیم دیدیم. اشاره هم کردیم که این تایپ، تایپِ واجب برای ‏‎main‎‏ در یه فایلِ منبع ِه. دلیل‌ش اینه که اجرا ِ ‏‎main‎‏ فقط اثرِ جانبی تولید می‌کنه.

به ساده‌ترین شکل ممکن بگیم، اجرای یک اجراییه ِ I/O (ورودی/خروجی معمولاً بدون خط مورب نوشته میشه: ‏‎‘IO’‎‏؛ وقتی برای نوع‌داده ِ توی هسکل استفاده میشه، خط مورب نداره) عوارضِ جانبی داره (مثل خوندن از ورودی و یا چاپ به صفحه) و شاملِ یک مقدارِ خروجی‌ه. اون پرانتز ‏‎()‎‏ یه توپل ِ خالی‌ه، که بهش میگیم واحد. واحد، هم یه مقدار، و هم یه تایپ با همین یک سَکَنه‌ست، که در واقع "هیچ چیز" رو نشون میده. چاپ ِ یه نوشته به ترمینال، مقدارِ خروجیِ با مفهومی نداره. ولی یه اجراییه ِ ‏‎IO‎‏، مثل هر بیانیه‌ی دیگه در هسکل، نمی‌تونه بی‌نتیجه باشه؛ باید یه چیزی برگردونه. پس از این توپل ِ خالی برای مقدار برگشتی این اجراییه ِ I/O استفاده می‌کنیم. بنابراین، تابعِ ‏‎print‎‏ اول اجراییه ِ I/O برای چاپ ِ نوشته به ترمینال رو اجرا می‌کنه، و بعد با برگردوندن این توپل ِ خالی، اجراییه رو کامل و پایان اجرای تابع رو مشخص می‌کنه. توپل ِ خالی رو چاپ نمی‌کنه، ولی به طورِ ضمنی وجود داره. ساده‌ترین توصیف برای تفاوت یه تایپ عادی مثل ‏‎String‎‏ و همون تایپ با ‏‎IO‎‏ (مثل ‏‎IO String‎‏) اینه که بگیم اجراییه‌های ‏‎IO‎‏ یه جور فرمول‌اند. یه مقدار با تایپ ‏‎IO String‎‏ در واقع یه راه برای تولید یه ‏‎String‎‏ ِه، که امکان داره در این راه قبل از رسیدن به مقدار نهایی نیاز به پیاده‌سازیِ یک یا چند اثرِ جانبی هم داشته باشه.

این یه مقدار ‏‎String‎‏ ِه:

myVal :: String

اما این یکی مقدار یه متود یا راه برای بدست آوردنِ یه مقدارِ ‏‎String‎‏ با اجرای اثرات یا I/O ِه:

ioString :: IO String

همونطور که قبلاً دیدیم، هر وقت ‏‎main‎‏ رو در برنامه‌مون صدا می‌زنیم یک اجراییه ِ ‏‎IO‎‏ اجرا میشه. اما با صدا زدن تابع ‏‎print‎‏ هم (چه صراحتاً چه غیرمستقیم) یه اجراییه ِ ‏‎IO‎‏ رو پیاده می‌کنیم.

کار با ‏‎Show‎‏

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

برای تعریف یه نمونه از ‏‎Show‎‏، تعریف یکی از توابع ‏‎show‎‏ یا ‏‎showsPrec‎‏ کفایت می‌کنه، مثل زیر:

data Mood = Blah

instance Show Mood where
  show _ = "Blah"
Prelude> Blah
Blah

اگه یه نوع‌داده رو بدون یه نمونه از ‏‎Show‎‏ تعریف کنین، و درخواست چاپ ِش رو به GHCi بدین، این پیغام رو می‌گیرین:

Prelude> data Mood = Blah
Prelude> Blah

No instance for (Show Mood) arising
  from a use of ‘print’
In a stmt of an interactive GHCi command: print it

تایپکلاس ‏‎Show‎‏ یکی از تایپکلاس‌هاییه که GHC مشتق گرفتن ِش رو پشتیبانی می‌کنه. اینطوری یه نوع‌داده رو با ‏‎Show‎‏ تعریف می‌کنیم:

Prelude> data Mood = Blah deriving Show
Prelude> Blah
Blah

بیشترِ مواقع هم همین کفایت می‌کنه. با این حال، جلوتر تو فصلی که پروژه می‌سازیم، لازم میشه یه نمونه ِ اختصاصی از ‏‎Show‎‏ تعریف کنیم که جذابیت بیشتری داره!