درس ۱۵ از ۴۹ · داده‌ها و گونه‌ها

جداشمارها

جداشمار گونه‌ای است با مجموعه‌ی بسته‌ای از حالت‌ها: چراغ راهنما یا قرمز است یا سبز یا زرد، و هیچ حالت دیگری ندارد. چون کامپایلر همه‌ی حالت‌ها را می‌شناسد، اگر کدتان یکی را فراموش کند به شما می‌گوید. هر حالت می‌تواند داده‌ی خودش را هم داشته باشد، و همین جداشمار را راه طبیعی گفتن «یکی از این چند نوع، با این جزئیات» می‌کند.

جداشمار ساده

حالت‌ها با ویرگول جدا می‌شوند و از صفر شماره می‌گیرند. می‌توانید به هر حالت مقدار دلخواه بدهید و حالت‌های بعدی از همان‌جا ادامه می‌دهند. با برگردان صحیح شماره‌ی هر حالت را می‌گیرید.

enum-values.salam
جداشمار وضعیت:
    پیش‌نویس، منتشرشده = ۱۰، بایگانی
پایان

تابع ریشه:
    حالت := وضعیت.منتشرشده
    سرچاپ حالت برگردان صحیح، وضعیت.بایگانی برگردان صحیح
    سرچاپ حالت == وضعیت.منتشرشده
پایان
خروجی10 11 true

.نام() نام هر حالت را به شکل رشته می‌دهد و .شمار() روی خود جداشمار، تعداد حالت‌ها را:

enum-name.salam
جداشمار فصل: بهار، تابستان، پاییز، زمستان پایان

تابع ریشه:
    ف := فصل.پاییز
    سرچاپ ف.نام()
    سرچاپ "شمار فصل‌ها:", فصل.شمار()
    سرچاپ ف == فصل.پاییز, ف برگردان صحیح
پایان
خروجیپاییز شمار فصل‌ها: 4 true 2

جداشمار و همخوان

در همخوان روی جداشمار، حالت‌ها را با نام خالی‌شان می‌نویسید. همخوان باید همه‌ی حالت‌ها را پوشش دهد یا با وگرنه تمام شود؛ اگر یکی جا بیفتد برنامه کامپایل نمی‌شود: E080: همخوان ناقص روی جداشمار 'چراغ': عضو(های) زرد پوشش داده نشده است. همین بررسی دلیل اصلی استفاده از جداشمار به‌جای عدد یا رشته‌ی ساده است. اگر بعدها حالتی اضافه کنید، کامپایلر همه‌ی همخوان‌هایی را که باید آن را یاد بگیرند نشانتان می‌دهد.

enum-state.salam
جداشمار چراغ: قرمز، سبز، زرد پایان

تابع بعدی(چ: چراغ): چراغ:
    برگشت همخوان چ:
        قرمز: چراغ.سبز پایان
        سبز: چراغ.زرد پایان
        زرد: چراغ.قرمز پایان
    پایان
پایان

تابع ثانیه(چ: چراغ): صحیح:
    برگشت همخوان چ:
        قرمز: ۳۰ پایان
        سبز: ۲۵ پایان
        زرد: ۵ پایان
    پایان
پایان

تابع ریشه:
    ناپایا چراغ راهنما := چراغ.قرمز
    ناپایا چرخه := ۰
    تکرار چراغ.شمار():
        سرچاپ چراغ راهنما.نام()، ثانیه(چراغ راهنما)
        چرخه += ثانیه(چراغ راهنما)
        چراغ راهنما = بعدی(چراغ راهنما)
    پایان
    سرچاپ "یک چرخه‌ی کامل:"، چرخه، "ثانیه، و دوباره"، چراغ راهنما.نام()
پایان
خروجیقرمز 30 سبز 25 زرد 5 یک چرخه‌ی کامل: 60 ثانیه، و دوباره قرمز

هر تابع یک واقعیت را درباره‌ی همه‌ی حالت‌ها می‌گوید و چراغ.شمار() باعث می‌شود حلقه دقیقاً یک دور بزند، هر چند حالت که باشد. ماشین حالت کوچکی مثل این، که هر حالتش حالت بعدی را می‌شناسد، همه‌جا پیدا می‌شود: پردازش سفارش، نوبت بازی، وضعیت اتصال.

وگرنه را فقط وقتی بنویسید که حالت‌های باقی‌مانده واقعاً یک پاسخ دارند. یک وگرنهِ همه‌گیر حالت‌هایی را هم که بعدها اضافه می‌کنید می‌بلعد و کامپایلر دیگر نمی‌تواند یادآوری‌شان کند.

جداشمار در ساختار و مقدار ذخیره‌شده

جداشمار گونه‌ی خوبی برای فیلدی از ساختار است که مجموعه‌ی ثابتی از حالت‌ها دارد. در چاپ ساختار، این فیلد به شکل عددش نشان داده می‌شود؛ پس هر جا قرار است آدم آن را بخواند از نام() استفاده کنید.

enum-order.salam
جداشمار وضعیت: ثبت، آماده، ارسال، تحویل پایان

ساختار سفارش:
    همگانی شماره: صحیح = ۰
    همگانی حالت: وضعیت = وضعیت.ثبت
پایان

تابع وضعیت از کد(کد: صحیح): وضعیت:
    اگر کد < ۰ یا کد >= وضعیت.شمار():
        برگشت وضعیت.ثبت
    پایان
    برگشت کد برگردان وضعیت
پایان

تابع ریشه:
    ناپایا س := سفارش { شماره = ۱۰۴۲ }
    س.حالت = وضعیت.آماده
    سرچاپ س
    سرچاپ س.شماره، س.حالت.نام()

    ذخیره := س.حالت برگردان صحیح
    سرچاپ "ذخیره به شکل"، ذخیره، "و خوانده به شکل"، وضعیت از کد(ذخیره).نام()
    سرچاپ "بی‌بررسی:"، "[" + (۹ برگردان وضعیت).نام() + "]"، "با بررسی:"، وضعیت از کد(۹).نام()
پایان
خروجیسفارش {شماره = 1042, حالت = 1} 1042 آماده ذخیره به شکل 1 و خوانده به شکل آماده بی‌بررسی: [] با بررسی: ثبت

همین عدد است که در پرونده یا پایگاه داده ذخیره می‌کنید و کد برگردان وضعیت آن را دوباره به حالت تبدیل می‌کند. این تبدیل بررسی نمی‌شود: ۹ برگردان وضعیت پذیرفته می‌شود با این‌که هیچ حالتی مقدار ۹ ندارد، و نام() آن رشته‌ی خالی است. وقتی عدد از بیرون برنامه می‌آید، اول بررسی‌اش کنید، همان‌طور که وضعیت از کد با وضعیت.شمار() این کار را می‌کند. اگر حالت‌ها مقدار دلخواه دارند، مثل منتشرشده = ۱۰، با همان مقدارها مقایسه کنید.

جداشمار داده‌دار

هر حالت می‌تواند داده‌های نام‌دار داشته باشد، مثل شعاع برای دایره. آن را با شکل.دایره(۲.۰) بسازید و با همخوان حالت را تشخیص دهید و داده‌هایش را بیرون بکشید. هر شاخه می‌تواند شرط اضافه‌ای با اگر داشته باشد. شاخه‌ی شرط‌دار پوشش آن حالت به حساب نمی‌آید، پس بعد از آن یک شاخه‌ی بی‌شرط برای همان حالت نگه دارید.

enum-data.salam
جداشمار شکل:
    دایره(شعاع: اعشار۶۴)
    مستطیل(پهنا: اعشار۶۴، بلندی: اعشار۶۴)
    هیچ
پایان

تابع مساحت(ش: شکل): اعشار۶۴:
    برگشت همخوان ش:
        دایره(ر): ۳.۱۴ * ر * ر پایان
        مستطیل(پ، ب) اگر پ == ب: پ * پ پایان
        مستطیل(پ، ب): پ * ب پایان
        هیچ: ۰.۰ پایان
    پایان
پایان

تابع ریشه:
    سرچاپ مساحت(شکل.دایره(۲.۰))
    سرچاپ مساحت(شکل.مستطیل(۳.۰، ۴.۰))
    سرچاپ مساحت(شکل.هیچ)
    سرچاپ شکل.دایره(۱.۵)
پایان
خروجی12.56 12 0 دایره(شعاع = 1.5)

در الگو، _ مقداری را که لازم ندارید رد می‌کند و بن ب کل حالت را به یک نام می‌بندد تا فیلدهایش را با ب.کد بخوانید. حالت‌های داده‌دار با == روی همه‌ی مقدارهایشان مقایسه می‌شوند و نام() و شمار() مثل جداشمار ساده کار می‌کنند:

enum-payment.salam
جداشمار پرداخت:
    نقدی
    کارت(چهاررقم: رشته، مبلغ: صحیح)
    بن(کد: رشته)
پایان

تابع برچسب(پ: پرداخت): رشته:
    برگشت همخوان پ:
        نقدی: "نقدی" پایان
        کارت(رقم‌ها، _): "کارت با پایان " + رقم‌ها پایان
        بن ب: "بن " + ب.کد پایان
    پایان
پایان

تابع ریشه:
    پرداخت‌ها := [پرداخت.نقدی، پرداخت.کارت("۴۲۴۲"، ۱۲۰)، پرداخت.بن("بهار")]
    هر پ در پرداخت‌ها:
        سرچاپ پ.نام()، "->"، برچسب(پ)
    پایان
    سرچاپ پرداخت.کارت("۴۲۴۲"، ۱۲۰) == پرداخت‌ها[۱]، پرداخت‌ها[۰] == پرداخت‌ها[۲]
    سرچاپ "شمار روش‌های پرداخت:"، پرداخت.شمار()
پایان
خروجینقدی -> نقدی کارت -> کارت با پایان ۴۲۴۲ بن -> بن بهار true false شمار روش‌های پرداخت: 3

جداشمار داده‌دار دست‌کم دو حالت لازم دارد و حالت‌هایش نمی‌توانند = مقدار هم داشته باشند.

جداشمار عام

جداشمار داده‌دار هم می‌تواند گونه‌ی عام بگیرد. شاید<ت> یا مقداری دارد یا هیچ؛ راهی امن برای گفتن «نتیجه‌ای در کار نیست» بدون پوچ.

enum-generic.salam
جداشمار شاید<ت>:
    دارد(مقدار: ت)
    هیچ
پایان

تابع تقسیم(الف: صحیح، ب: صحیح): شاید<صحیح>:
    اگر ب == ۰:
        برگشت شاید.هیچ
    پایان
    برگشت شاید.دارد(الف / ب)
پایان

تابع شرح(ش: شاید<صحیح>): رشته:
    برگشت همخوان ش:
        دارد(م): "حاصل " + م پایان
        هیچ: "تقسیم بر صفر" پایان
    پایان
پایان

تابع ریشه:
    سرچاپ شرح(تقسیم(۱۰، ۲))
    سرچاپ شرح(تقسیم(۱، ۰))
پایان
خروجیحاصل 5 تقسیم بر صفر

خود پارامترهای گونه در درس تابع‌های عام و میانجی‌ها آمده‌اند.

نگه داشتن نتیجه به عنوان یک مقدار

خطاها معمولاً پرتاب و گرفته می‌شوند (درس قاعده‌ها را ببینید)، اما گاهی می‌خواهید نتیجه را مثل یک مقدار نگه دارید، ذخیره کنید یا پیش از بررسی جابه‌جا کنید. جداشمار دوحالتی خودتان را بسازید، موفق با یک مقدار و خطا با یک خطا، همان شکل شاید<ت> در بالا ولی با داده روی هر دو حالت. درون تابعی که چنین جداشماری برمی‌گرداند، ? پس از یک فراخوانی مقدار موفق را بیرون می‌کشد و اگر خطا بود، همان خطا را بی‌درنگ برمی‌گرداند؛ ? باید کل مقدار دستورش باشد: ا := ر()?، ا = ر()?، ر()? یا برگشت ر()?. وقتی ? زودتر برمی‌گرداند، دیرکنهای تابع باز هم اجرا می‌شوند. نسخه‌ی انگلیسی این درس یک نمونه‌کد کامل از این الگو دارد.

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

چند قاعده‌ی کاربردی

  • هر جا مقداری یکی از فهرستی شناخته‌شده و ثابت است، از جداشمار استفاده کنید. رشته ممکن است بی‌آن‌که کسی بفهمد غلط نوشته شود (مثلاً "ارسل" به‌جای "ارسال")، ولی حالت نادرست جداشمار کامپایل نمی‌شود.
  • روی جداشمارهای خودتان همخوانِ بی وگرنه را ترجیح دهید تا کامپایلر هر جایی را که حالت تازه باید آن‌جا رسیدگی شود پیدا کند.
  • عددهایی را که از بیرون برنامه می‌آیند، پیش از تبدیل با برگردان بررسی کنید.
  • به هر حالت همان داده‌ای را بدهید که مال خودش است، به‌جای یک ساختار با فیلدهایی که فقط گاهی استفاده می‌شوند.
  • تابع‌هایی را که ممکن است شکست بخورند خطادار کنید؛ یک جداشمار موفق/خطا خودتان را فقط برای نتیجه‌هایی بسازید که می‌خواهید مثل یک مقدار ذخیره یا جابه‌جا کنید.
  • وقتی مقدار هر یک از چند گونه‌ی ساده و بی‌ربط باشد، الف یا ب را به کار ببرید؛ وقتی حالت‌ها یک ایده با چند حالت‌اند، جداشمار را.