طراحی نحو زبان سلام: خوانایی، سادگی و توانایی

چرا کد سلام این شکلی است: تصمیم‌های پشت بلوک‌ها، تعریف متغیرها، حلقه‌ها، تطبیق الگو و نام‌ها، هزینه‌ی هر کدام و مقایسه‌شان با نحو پایتون، Go، Ruby و Lua.

در این مقاله
  1. اصول پشت نحو زبان سلام
  2. بلوک‌ها: دونقطه برای باز کردن، پایان برای بستن
  3. تعریف متغیر: :=، ناپایا و پایا
  4. واژه‌های کلیدی کوتاه برای کارهای پرتکرار
  5. حلقه‌ها: تا، تکرار و هر
  6. همخوان و ترابرد (match و switch)
  7. نام‌های فاصله‌دار در زبان سلام
  8. واژه برای منطق، و یک راه برای به‌روز کردن مقدار
  9. سلام کنار پایتون، Go، Ruby و Lua
  10. نحو سلام چه هزینه‌ای دارد
  11. پرسش‌های پرتکرار درباره‌ی نحو زبان سلام

نحو زبان برنامه‌نویسی سلام بیشتر برای خوانده شدن طراحی شده تا نوشته شدن: بلوک‌ها با دونقطه باز و با پایان (end) بسته می‌شوند، متغیرها با := تعریف می‌شوند و تا با ناپایا (mut) علامت نخورند تغییرناپذیرند، تابع‌ها با برگشت (ret) مقدار برمی‌گردانند و منطق با واژه‌های و، یا و وارونه (and، or، not) نوشته می‌شود. هدف کدی است که نوآموز بتواند بلند بخواند، برنامه‌نویس باتجربه بتواند سریع از رویش بگذرد، و یک به یک بر املای دوم، یعنی املای فارسی، نگاشت شود، بی آنکه گونه‌های ایستا، تطبیق الگو یا کنترل سطح پایین از دست برود. هر جا سلام ناچار بوده میان کوتاهی و روشنی انتخاب کند، معمولاً روشنی را برگزیده، و هر جا میان انعطاف و یک راه بدیهی، معمولاً یک راه بدیهی را.

این مقاله تصمیم‌های اصلی نحوی را مرور می‌کند، آن‌ها را در برنامه‌های قابل اجرا نشان می‌دهد، با پایتون، Go، Ruby و Lua مقایسه می‌کند و صادقانه می‌گوید هر تصمیم چه هزینه‌ای دارد. نمونه‌ها به سلام فارسی نوشته شده‌اند و معادل انگلیسی هر واژه‌ی کلیدی کنارش آمده است.

اصول پشت نحو زبان سلام

چهار ایده بیشتر نحو سلام را توضیح می‌دهد:

  1. بلوک‌ها باید دیده شوند. باید بتوانید پایان یک بلوک را ببینید، بی آنکه فاصله بشمارید یا آکولادها را جفت کنید.
  2. پیش‌فرض باید انتخاب امن باشد. مقدارها تا خودتان نگویید عوض نمی‌شوند؛ شرط‌ها باید منطقی واقعی باشند؛ عددها بی‌صدا معنای «درست» نمی‌دهند.
  3. یک راه بدیهی. هر جا دو شکل نوشتن همان کار را انجام دهند، سلام اغلب فقط یکی را مجاز می‌داند و دیگری را خطایی همراه با اصلاح می‌کند.
  4. دو املا، یک دستور زبان. هر ساخت باید هم به انگلیسی و هم به فارسی طبیعی خوانده شود، چون املای فارسی نگاشت توکن به توکن املای انگلیسی است (نگاه کنید به یک زبان، دو خط). این قاعده نحوی را که به ترتیب کلمات انگلیسی یا به نمادهایی که در متن راست به چپ بد رفتار می‌کنند تکیه دارد کنار می‌گذارد.

بلوک‌ها: دونقطه برای باز کردن، پایان برای بستن

هر بلوک در سلام با : باز و با پایان بسته می‌شود: تابع، شرط، حلقه، ساختار، جداشمار و شاخه‌های همخوانی. بلوک کوتاه می‌تواند در یک سطر بیاید.

سلام
تابع علامت عدد(ن: صحیح): رشته:
    اگر ن < ۰: برگشت "منفی" پایان
    اگر ن == ۰: برگشت "صفر" پایان
    برگشت "مثبت"
پایان

تابع ریشه:
    هر ن در [-۴، ۰، ۹]:
        سرچاپ ن، علامت عدد(ن)
    پایان
پایان
خروجی-4 منفی 0 صفر 9 مثبت

این شیوه میان پایتون و Ruby می‌نشیند. پایتون بلوک را با دونقطه باز و با کم کردن تورفتگی می‌بندد؛ تمیز است، اما پایان بلوک نامرئی می‌شود و کد کپی‌شده شکننده. Ruby و Lua بلوک را با end می‌بندند، اما به چند شکل مختلف باز می‌کنند (do، then یا هیچ). سلام دونقطه را از پایتون و end را از Ruby و Lua گرفته و همین جفت را همه‌جا به کار می‌برد. تورفتگی قراردادی است و salam format آن را یکدست می‌کند، اما معنایی ندارد، پس بلوکی که در عمق نادرست جاگذاری شود باز هم همان‌طور تجزیه می‌شود.

آکولاد برای بلوک به کار نمی‌رود؛ در سلام { } مال ساختارهای لفظی (نقطه { ایکس = ۱ }) و ظرف‌های خالی لفظی است. املای انگلیسی همین شکل را دارد: : و end.

تعریف متغیر: :=، ناپایا و پایا

سلام
پایا بیشینه_کاربر := ۱۰۰

تابع ریشه:
    نام := "سارا"
    ناپایا بازدید := ۰
    بازدید++
    بازدید += ۲
    نسبت := بازدید برگردان اعشار۶۴ / بیشینه_کاربر
    عدد بزرگ := ۳۰۰۰۰۰۰۰۰۰ برگردان صحیح۶۴
    سرچاپ نام، بازدید، نسبت، عدد بزرگ
پایان
خروجیسارا 3 0.03 3000000000

:= مثل Go متغیر را تعریف و گونه‌اش را استنتاج می‌کند. اما برخلاف Go، نتیجه تغییرناپذیر است: نام هرگز دوباره مقداردهی نمی‌شود و تلاش برای این کار خطای کامپایل است، نه هشدار:

متن
خطا[خطا۰۱۳]: نمی‌توان متغیر تغییرناپذیر 'سقف' را دوباره مقداردهی کرد؛ برای این کار آن را 'ناپایا' تعریف کنید، یا به‌جای آن به عناصر یا فیلدهایش مقدار دهید

ناپایا (mut) تغییر را صریحاً اجازه می‌دهد؛ همان ایده‌ی let mut در Rust یا var در Swift. خواننده می‌تواند مطمئن باشد نامی که ناپایا ندارد مقدارش را نگه می‌دارد، و همین دنبال کردن تابع‌های بلند را بسیار ساده‌تر می‌کند.

برای متغیر محلی شکل ک: صحیح = ۵ وجود ندارد. سلام زمانی تعریف با گونه‌ی صریح داشت و آن را حذف کرد: وقتی گونه‌ای جز گونه‌ی استنتاج‌شده می‌خواهید، برگردان (as) می‌نویسید، مثل ۳۰۰۰۰۰۰۰۰۰ برگردان صحیح۶۴ در برنامه‌ی بالا. این کار دقیقاً یک راه برای تعریف متغیر محلی باقی می‌گذارد. نوشتن گونه هر جا که اطلاعاتی می‌دهد که قابل استنتاج نیست باقی مانده است: پارامترها، گونه‌ی برگشتی و فیلدهای ساختار.

واژه‌های کلیدی کوتاه برای کارهای پرتکرار

در املای انگلیسی واژه‌هایی که بیشتر تایپ می‌شوند کوتاه‌اند: func، ret، mut، pub، end. واژه‌هایی که کمتر تایپ می‌شوند کامل نوشته می‌شوند: struct، enum، interface، defer، match. این همان بده‌بستانی است که Go با func انجام داد، و دلیل اینکه return در سلام ret است: در یک تابع معمولی چند بار تکرار می‌شود. املای فارسی همین واژه‌ها را با معادل‌های آشنای فارسی می‌نویسد: تابع، برگشت، ناپایا، همگانی، پایان، و برای دسته‌ی دوم ساختار، جداشمار، میانجی، دیرکن، همخوان.

سرچاپ و چاپ (println و print) دستورند، نه فراخوانی تابع، و آرگومان‌هایشان را بدون پرانتز می‌گیرند: سرچاپ نام، بازدید. آرگومان‌ها با فاصله کنار هم چاپ می‌شوند، همان چیزی که نوآموز از نخستین برنامه‌اش انتظار دارد و یک سطر اشکال‌زدایی معمولاً لازم دارد.

حلقه‌ها: تا، تکرار و هر

سلام سه حلقه دارد و نه while دارد و نه for به سبک C:

سلام
تابع ریشه:
    ناپایا ن := ۲۷
    ناپایا گام‌ها := ۰
    تا ن != ۱:
        ن = ن % ۲ == ۰ ? ن / ۲ : ۳ * ن + ۱
        گام‌ها++
    پایان
    سرچاپ "گام‌های کولاتز برای ۲۷:"، گام‌ها
    تکرار ۲ در ای:
        سرچاپ "تکرار"، ای
    پایان
    تکرار ۱۰ تا ۰ هر ۵ در ای:
        سرچاپ "نزولی"، ای
    پایان
    هر ای، نام در ["سارا"، "علی"]:
        سرچاپ ای، نام
    پایان
پایان
خروجیگام‌های کولاتز برای ۲۷: 111 تکرار 0 تکرار 1 نزولی 10 نزولی 5 نزولی 0 0 سارا 1 علی
  • تکرار (repeat) شمارش را پوشش می‌دهد: تکرار ۳ سه بار اجرا می‌شود، تکرار ن در ای از صفر می‌شمارد، تکرار ۱ تا ۵ دو سر بازه را شامل می‌شود و هر گام را تعیین می‌کند. جهت را مرزها تعیین می‌کنند، پس تکرار ۱۰ تا ۰ رو به پایین می‌شمارد.
  • هر (each) آرایه، وکتور، نگاشت، رشته و گونه‌های خود شما را پیمایش می‌کند، با اندیس یا کلید اختیاری.
  • تا (until) حلقه‌ی عمومی است و تنها تصمیم این فهرست که آدم‌ها سرش زمین می‌خورند: until c: تا وقتی c درست است اجرا می‌شود. واژه‌ی انگلیسی خلاف این را القا می‌کند. املای فارسی‌اش، تا، به‌طور طبیعی «تا وقتی که» معنا می‌دهد، پس در فارسی همین واژه‌ی کلیدی درست خوانده می‌شود. مقاله‌ی طراحی برای فارسی این بده‌بستان را توضیح می‌دهد؛ توصیه‌ی عملی این است که until را در کد انگلیسی «while» بخوانید.

حذف حلقه‌ی سه‌بخشی for در C یک منبع کلاسیک خطای یکی‌کم‌یکی‌زیاد (Off-by-one) را از میان برمی‌دارد و نیت هر حلقه را در نخستین واژه‌اش نشان می‌دهد. درس حلقه‌ها همه‌ی شکل‌ها را پوشش می‌دهد.

همخوان و ترابرد (match و switch)

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

سلام
جداشمار شکل:
    دایره(شعاع: اعشار۶۴)
    مستطیل(پهنا: اعشار۶۴، بلندی: اعشار۶۴)
پایان

تابع توصیف(ش: شکل): رشته:
    برگشت همخوان ش:
        دایره(ر): "دایره، شعاع " + ر پایان
        مستطیل(پ، ب) اگر پ == ب: "مربع، ضلع " + پ پایان
        مستطیل(پ، ب): "مستطیل، " + پ + " در " + ب پایان
    پایان
پایان

تابع رتبه(نمره: صحیح): رشته:
    ترابرد نمره:
        ۹۰ تا ۱۰۰: برگشت "الف" پایان
        ۷۵ تا ۸۹: برگشت "ب" پایان
        وگرنه: برگشت "ج" پایان
    پایان
    برگشت "?"
پایان

تابع ریشه:
    شکل‌ها := [شکل.دایره(۱.۵)، شکل.مستطیل(۲.۰، ۲.۰)، شکل.مستطیل(۳.۰، ۱.۰)]
    هر ش در شکل‌ها:
        سرچاپ توصیف(ش)
    پایان
    هر نمره در [۹۵، ۸۰، ۴۰]:
        سرچاپ نمره، رتبه(نمره)
    پایان
پایان
خروجیدایره، شعاع 1.5 مربع، ضلع 2 مستطیل، 3 در 1 95 الف 80 ب 40 ج

همخوان (match) یک عبارت است: مقدار تولید می‌کند، جداشمارهایی را که داده حمل می‌کنند باز می‌کند، نگهبان (اگر پ == ب) را پشتیبانی می‌کند و باید همه‌ی حالت‌ها را پوشش دهد، وگرنه برنامه کامپایل نمی‌شود. این ساخت برای کار با داده است، به روح match در Rust یا switch در Swift.

ترابرد (switch) دستوری برای جریان کنترل روی مقدارهای ساده است، با بازه (۹۰ تا ۱۰۰)، فهرست جداشده با ویرگول و برچسب‌های مقایسه‌ای مثل > 60. ریزش به شاخه‌ی بعد (Fallthrough) به سبک C دارد، مگر آنکه بشکن یا برگشت بنویسید؛ این برای کد منتقل‌شده از زبان‌های دیگر مفید و دامی شناخته‌شده است، پس یک بررسی اختیاری (Lint) زنجیره‌های بلند اگر را که باید ترابرد باشند علامت می‌زند. هر دو همان شکل برچسب: ... پایان بقیه‌ی بلوک‌ها را دارند. درس همخوان و درس جداشمارها جلوتر می‌روند.

نام‌های فاصله‌دار در زبان سلام

نام در سلام می‌تواند فاصله داشته باشد: علامت عدد، جمع ردیف، آخر هفته است. در این صورت فراخوانی مثل یک عبارت خوانده می‌شود: علامت عدد(ن). این امکان بیشتر برای فارسی وجود دارد، که بسیاری از مفهوم‌ها در آن به‌طور طبیعی دوکلمه‌ای‌اند و چسباندنشان نادرست به نظر می‌رسد، اما در انگلیسی هم کار می‌کند (sign of، line total).

دو قاعده آن را بی‌ابهام نگه می‌دارد. کلمه‌های یک نام نمی‌توانند واژه‌ی کلیدی باشند (فاصله تا مبدا کار نمی‌کند، چون تا واژه‌ی کلیدی است؛ در انگلیسی هم distance to origin به خاطر to همین‌طور)، و نام‌های پایا (const) باید یک‌کلمه‌ای باشند. برخی تیم‌ها در کد انگلیسی همچنان snake_case را ترجیح می‌دهند و این هم اشکالی ندارد؛ زیرخط نویسه‌ای معمولی است.

واژه برای منطق، و یک راه برای به‌روز کردن مقدار

منطق با واژه نوشته می‌شود: و، یا، وارونه (در انگلیسی and، or، not). نمادهای &&، || و ! عملگر سلام نیستند و کامپایلر همین را می‌گوید و واژه‌ی درست را نام می‌برد:

متن
خطا: '&&' عملگر سلام نیست؛ به جای آن 'و' بنویسید

شرط‌ها باید منطقی واقعی باشند. اگر ن: با یک ن صحیح خطای E021 است («شرط 'اگر' باید بولی باشد»)، پس چیزی به نام «درست‌نمایی» مقدارها وجود ندارد که لازم باشد به خاطر بسپارید. مقایسه‌ها را هم می‌شود با واژه نوشت (بزرگتر، کوچکتر، برابر)، که در فارسی اهمیت دارد، چون < و > در متن راست به چپ قرینه نمایش داده می‌شوند.

سلام درباره‌ی به‌روز کردن یک متغیر بر حسب خودش سخت‌گیر است. ک = ک + ۱ خطایی همراه با اصلاح است:

متن
خطا[خطا۱۶۲]: 'شمار = شمار + ۱' مجاز نیست: بنویسید 'شمار++'

قاعده یک املا برای هر عملیات است: ک++ و ک-- برای گام یک، و ک += ن و هم‌خانواده‌هایش برای بقیه. یک روز ایرادگیرانه به نظر می‌رسد و بعد از ذهن می‌رود؛ سودش این است که هر به‌روزرسانی در کل کد یک شکل دارد و بازبینی و جست‌وجوی کد ساده‌تر می‌شود. متغیر بی‌استفاده هم به‌جای هشدار، خطاست.

سلام کنار پایتون، Go، Ruby و Lua

یک تابع، جمع عددهای زوج یک فهرست، در پنج زبان. پایتون:

Python
def sum_even(numbers):
    total = 0
    for n in numbers:
        if n % 2 == 0:
            total += n
    return total

Go:

Go
func sumEven(numbers []int) int {
    total := 0
    for _, n := range numbers {
        if n%2 == 0 {
            total += n
        }
    }
    return total
}

Ruby:

ruby
def sum_even(numbers)
  total = 0
  numbers.each do |n|
    total += n if n.even?
  end
  total
end

Lua:

lua
local function sum_even(numbers)
  local total = 0
  for _, n in ipairs(numbers) do
    if n % 2 == 0 then
      total = total + n
    end
  end
  return total
end

سلام:

سلام
تابع جمع زوج‌ها(عددها: صحیح[]): صحیح:
    ناپایا مجموع := ۰
    هر ن در عددها:
        اگر ن % ۲ == ۰:
            مجموع += ن
        پایان
    پایان
    برگشت مجموع
پایان

تابع ریشه:
    سرچاپ جمع زوج‌ها([۱، ۲، ۳، ۴، ۵، ۶])
پایان
خروجی12
ویژگیپایتونGoRubyLuaسلام
مرز بلوکدونقطه + تورفتگیآکولادend (با شروع‌های گوناگون)end (do، then)دونقطه + پایان (end)
تعریف متغیرانتساب:= یا varانتسابlocal:=
تغییرناپذیر به‌طور پیش‌فرضنهنهنهنهبله، با ناپایا تغییرپذیر
گونه‌ی ایستاراهنمای اختیاریبلهنهنهبله، استنتاج‌شده
شکل‌های حلقهfor، whileفقط foreach، while، untilfor، while، repeatهر، تکرار، تا
عملگرهای منطقیand or not&& || !هر دوand or notو یا وارونه (and or not)
تطبیق الگوmatch (از ۳٫۱۰)نداردcase/inنداردهمخوان، با پوشش کامل
کامپایل به کد بومینهبلهنهنهبله

نسخه‌ی سلام به پایتون و Lua نزدیک‌تر خوانده می‌شود، := و گونه‌های ایستای Go را نگه می‌دارد و پایان صریح و تغییرپذیری صریح را اضافه می‌کند. از نسخه‌ی Ruby، که به بلوک‌ها و برگشت ضمنی تکیه دارد، بلندتر است؛ سلام برگشت صریح را ترجیح می‌دهد.

نحو سلام چه هزینه‌ای دارد

هر انتخاب بهایی دارد و برخی بهاهای سلام واقعی‌اند:

  • until در املای انگلیسی یعنی while. واژه‌های آشنای انگلیسی انتظاری با خود می‌آورند و این یکی خلاف آن است. رایج‌ترین اشتباه هنگام انتقال کد از زبان‌های دیگر همین است؛ املای فارسی، تا، این مشکل را ندارد.
  • پایان پرحرف است. کد با تودرتویی زیاد سطرهای پایان پشت سر هم جمع می‌کند که پایتون لازمشان نداشت.
  • سخت‌گیری ساعت اول را کند می‌کند. خطا برای متغیر بی‌استفاده، ک = ک + ۱ و شرط غیرمنطقی تازه‌کارها را غافلگیر می‌کند، هرچند هر کدام اصلاح پیشنهادی دارند.
  • تغییرات پیش از نسخه‌ی ۱٫۰. نحو هنوز بین نسخه‌ها جابه‌جا می‌شود: تعریف محلی با گونه‌ی صریح و پیکان => لامبدا حذف شدند. کدی که برای نسخه‌ی قدیمی‌تر نوشته شده ممکن است ویرایش لازم داشته باشد.
  • نام فاصله‌دار غیرمعمول است. ابزارهایی که فرض می‌کنند شناسه فاصله ندارد، از الگوهای grep تا رنگ‌آمیزهای زبان‌های دیگر، دقت بیشتری لازم دارند.

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

پرسش‌های پرتکرار درباره‌ی نحو زبان سلام

آیا نحو زبان سلام شبیه پایتون است؟

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

آیا سلام آکولاد دارد؟

نه برای بلوک‌ها. هر بلوک با دونقطه باز و با پایان بسته می‌شود. آکولاد در ساختارهای لفظی مثل نقطه { ایکس = ۱، ایگرگ = ۲ } و ظرف‌های خالی لفظی دیده می‌شود.

چرا سلام به‌جای while از until و تا استفاده می‌کند؟

سلام واژه‌ی کلیدی while ندارد؛ until c: (در فارسی تا c:) تا وقتی c درست است تکرار می‌شود. املای فارسی، تا، به معنای «تا وقتی که» است و طبیعی خوانده می‌شود، اما واژه‌ی انگلیسی خلاف معنای معمولش خوانده می‌شود. آن را «while» بخوانید.

آیا نام متغیر در زبان سلام می‌تواند فاصله داشته باشد؟

بله. نام‌هایی مثل جمع ردیف یا آخر هفته است یک شناسه‌اند و این به‌ویژه در فارسی کمک می‌کند. نام چندکلمه‌ای نمی‌تواند واژه‌ی کلیدی داشته باشد و نام ثابت‌ها باید یک‌کلمه‌ای باشد.

زبان سلام

چرا دنیا هنوز به زبان‌های برنامه‌نویسی تازه نیاز دارد؟ فلسفه‌ی طراحی و هدف‌های زبان برنامه‌نویسی سلام

هر زبان موفق بیست سال اخیر برای برطرف کردن یک درد مشخص ساخته شد. این مقاله انگیزه‌ی ساخت Go، Rust، Swift، Kotlin و Zig را مرور می‌کند و بعد سراغ خلأیی می‌رود که سلام هدف گرفته و تصمیم‌های طراحی‌ای که از آن نتیجه می‌شود.

خواندن مقاله
زبان سلام

یک زبان، دو خط: برنامه‌نویسی فارسی و انگلیسی با یک کامپایلر در زبان سلام

سلام یک زبان است با دو املا. این مقاله نشان می‌دهد یک فایل فارسی و یک فایل انگلیسی چطور به یک کامپایلر می‌رسند، یک کتابخانه‌ی استاندارد را به اشتراک می‌گذارند، خطاها را به زبان خواننده گزارش می‌کنند و بسته‌های همدیگر را فرا می‌خوانند.

خواندن مقاله