درس ۱۰ از ۴۹ · تابع‌ها

تابع‌های بی‌نام

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

تابع بی‌نام

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

lambda.salam
تابع به‌کاربردن(ر: تابع (صحیح) صحیح، مقدار: صحیح): صحیح:
    برگشت ر(مقدار)
پایان

تابع ریشه:
    دوبرابر := (ع: صحیح): برگشت ع * ۲ پایان
    سرچاپ دوبرابر(۵)
    سرچاپ به‌کاربردن((ع: صحیح): برگشت ع * ع پایان، ۷)
    رده := (نمره: صحیح):
        قبول := نمره >= ۱۰
        برگشت قبول ? "قبول" : "مردود"
    پایان
    سرچاپ رده(۱۴)، رده(۸)
پایان
خروجی10 49 قبول مردود

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

گونه‌ی تابع

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

lambda-type.salam
گونه قاعده = تابع (صحیح) منطقی

تابع شمردن(مقدارها: صحیح[]، شرط: قاعده): صحیح:
    ناپایا ن := ۰
    هر م در مقدارها:
        اگر شرط(م):
            ن++
        پایان
    پایان
    برگشت ن
پایان

تابع ریشه:
    دماها := [۱۲، ۱۸، ۲۵، ۳۱، ۹، ۲۷]
    گرم := (د: صحیح): برگشت د >= ۲۵ پایان
    سرچاپ "روزهای گرم:"، شمردن(دماها، گرم)
    سرچاپ "روزهای سرد:"، شمردن(دماها، (د: صحیح): برگشت د < ۱۰ پایان)
پایان
خروجیروزهای گرم: 3 روزهای سرد: 1

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

فرستادن تابع نام‌دار

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

lambda-named.salam
تابع با مالیات(بها: صحیح): صحیح:
    برگشت بها + بها * ۹ / ۱۰۰
پایان

تابع اعمال روی همه(بهاها: صحیح[]، تغییر: تابع (صحیح) صحیح):
    هر ش، ب در بهاها:
        سرچاپ ش، ب، "->"، تغییر(ب)
    پایان
پایان

تابع ریشه:
    اعمال روی همه([۱۰۰، ۲۵۰]، (ب: صحیح): برگشت با مالیات(ب) پایان)
پایان
خروجی0 100 -> 109 1 250 -> 272

شکل صحیح۶۴ برای جاهایی است که به سبک C یک فراخوان (callback) می‌خواهند، مثل مسیرهای یک سرور وب. در کد سلام، پیچیدن در تابع بی‌نام راه درست است.

مرتب‌سازی با مقایسه‌گر

مقایسه‌گر تابعی است که دو عضو می‌گیرد و جواب می‌دهد: «آیا اولی باید پیش از دومی بیاید؟». مرتب سازی.مرتب سازی با یک وکتور را با آن مرتب می‌کند، پس ترتیب را خودتان تعیین می‌کنید: بر اساس نام، امتیاز، از بیشترین به کمترین یا هر چیز دیگر. مرتب سازی.مرتب سازی ترتیب‌نگه‌دار با هم همین کار را می‌کند و عضوهای برابر را به همان ترتیبی که داشتند نگه می‌دارد.

lambda-sort.salam
واردسازی مرتب سازی

ساختار بازیکن:
    همگانی نام: رشته
    همگانی امتیاز: صحیح
پایان

تابع ریشه:
    ناپایا تیم := وکتور {} برگردان وکتور<بازیکن>
    دیرکن تیم.آزادکن()
    تیم.بیفزا(بازیکن { نام = "سارا"، امتیاز = ۷۲ })
    تیم.بیفزا(بازیکن { نام = "امید"، امتیاز = ۹۱ })
    تیم.بیفزا(بازیکن { نام = "لنا"، امتیاز = ۸۵ })
    تیم.بیفزا(بازیکن { نام = "آرش"، امتیاز = ۸۵ })
    مرتب سازی.مرتب سازی با(تیم، (الف: بازیکن، ب: بازیکن): برگشت الف.نام < ب.نام پایان)
    هر ب در تیم:
        چاپ ب.نام، ""
    پایان
    سرچاپ ""
    مرتب سازی.مرتب سازی ترتیب‌نگه‌دار با(تیم، (الف: بازیکن، ب: بازیکن): برگشت الف.امتیاز > ب.امتیاز پایان)
    هر ب در تیم:
        سرچاپ ب.نام، ب.امتیاز
    پایان
پایان
خروجیآرش امید سارا لنا امید 91 آرش 85 لنا 85 سارا 72

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

برگرداندن تابع

تابع می‌تواند یک تابع بی‌نام بسازد و آن را برگرداند. تابع بی‌نام از مقدارهایی که به کار می‌برد کپی نگه می‌دارد، پس هر بار که تابع بیرونی صدا زده شود، تابع تازه‌ای ساخته می‌شود. این راه خوبی است برای ساختن یک خانواده تابع هم‌خانواده از روی یک دستور.

lambda-make.salam
تابع تخفیف(درصد: صحیح): تابع (صحیح) صحیح:
    برگشت (بها: صحیح): برگشت بها - بها * درصد / ۱۰۰ پایان
پایان

تابع ریشه:
    عضو := تخفیف(۱۰)
    حراج := تخفیف(۲۵)
    سرچاپ عضو(۲۰۰)، حراج(۲۰۰)
    سرچاپ عضو(۸۰)، حراج(۸۰)
پایان
خروجی180 150 72 60

تابع بی‌نام از بیرون چه برمی‌دارد

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

lambda-capture.salam
تابع ریشه:
    ناپایا سقف := ۳
    نمایش := (): برگشت سقف پایان
    سقف = ۱۰
    سرچاپ "سقف"، سقف، "است ولی تابع بی‌نام می‌بیند"، نمایش()
    ناپایا کلیک‌ها := ۰
    کلیک := ():
        کلیک‌ها++
        برگشت کلیک‌ها
    پایان
    سرچاپ کلیک()، کلیک()، کلیک()
    سرچاپ "کلیک‌ها در بیرون:"، کلیک‌ها
پایان
خروجیسقف 10 است ولی تابع بی‌نام می‌بیند 3 1 2 3 کلیک‌ها در بیرون: 0

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

lambda-share.salam
ناپایا جمع := ۰

تابع بازدید همه(مقدارها: صحیح[]، بازدید: تابع (صحیح)):
    هر م در مقدارها:
        بازدید(م)
    پایان
پایان

تابع ریشه:
    بازدید همه([۱۲۰، ۸۰، ۴۵]، (ب: صحیح):
        جمع += ب
    پایان)
    سرچاپ "جمع:"، جمع
پایان
خروجیجمع: 245

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

نگه‌داشتن تابع‌ها در وکتور

گونه‌ی تابع می‌تواند گونه‌ی عضوهای یک وکتور باشد؛ پس می‌شود فهرستی از گام‌ها را هنگام اجرا ساخت و بعد به ترتیب اجرا کرد. این‌جا هر گام مقدار را تغییر می‌دهد و به گام بعدی می‌سپارد.

lambda-pipeline.salam
تابع ریشه:
    ناپایا گام‌ها := وکتور {} برگردان وکتور<تابع (صحیح) صحیح>
    دیرکن گام‌ها.آزادکن()
    گام‌ها.بیفزا((ع: صحیح): برگشت ع * ۲ پایان)
    گام‌ها.بیفزا((ع: صحیح): برگشت ع + ۱۰ پایان)
    گام‌ها.بیفزا((ع: صحیح): برگشت ع / ۳ پایان)
    ناپایا مقدار := ۷
    هر گام در گام‌ها:
        مقدار = گام(مقدار)
        سرچاپ مقدار
    پایان
پایان
خروجی14 24 8

چند قاعده‌ی سرانگشتی

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