۶ - ۱۰تایپکلاس 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
تعریف کنیم که جذابیت بیشتری داره!