در Common Lisp به لیست آرگومانهای یک تابع، «لیست لامبدا»lambda-list میگویند.
یک لیست لامبدا میتواند آرگومانهایی از انواع مختلف داشته باشد.
این انواع مختلف با استفاده از «کلیدواژههای لیست لامبدا»lambda-list-keyword مشخص میشوند که همهشان با & شروع میشوند.
پرکاربردترین این انواع، آرگومانهای اختیاری، کلیدواژه و باقیمانده هستند.
هر پارامتری که در لیست لامبدا پس از یک کلیدواژهی مشخصِ لیست لامبدا بیاید، از آن نوع خواهد بود.
هر کلیدواژهی لیست لامبدا فقط یک بار میتواند در یک لیست لامبدا به کار برود.
لیستهای لامبدا در ساختارهای دیگری هم به کار میروند که بعداً دربارهشان صحبت میکنیم، مثل ساختارشکنی و ماکروها.
در Common Lisp یک تابع میتواند چند آرگومان اختیاری داشته باشد.
این آرگومانها در لیست لامبدا با کلیدواژهی &optional مشخص میشوند.
اگر پارامتری مشخص نشود، به مقدار nil مقید میشود.
اگر چند پارامتر اختیاری وجود داشته باشد، به ترتیب مقید میشوند.
برای پارامترهای اختیاری میتوان مقادیر پیشفرض تعیین کرد.
در نهایت، برای هر پارامتر اختیاری میتوان نمادی تعیین کرد که بسته به اینکه فراخوانیکنندهی تابع آن پارامتر را فراهم کرده باشد یا نه، به «درست» یا «غلط» مقید میشود (به این نماد «پارامتر supplied-p» میگویند).
(defun optional-parameters (&optional x (y 'default) (z nil z-supplied-p))
(list x y (if z-supplied-p (list :z-was-supplied z)
(list :z-was-not-supplied z))))
(optional-parameters) ; => (NIL DEFAULT (:Z-WAS-NOT-SUPPLIED NIL))
(optional-parameters 5 nil 10) ; => (5 NIL (:Z-WAS-SUPPLIED 10))
در Common Lisp یک تابع میتواند پارامترهای نامدار داشته باشد (که به آنها «پارامترهای کلیدواژه» میگویند).
این پارامترها در لیست لامبدا با کلیدواژهی &key مشخص میشوند.
پارامترهای کلیدواژه، پارامترهای الزامی نیستند.
مثل پارامترهای اختیاری، میتوان برایشان مقادیر پیشفرض و نمادهایی تعیین کرد که به وضعیت «فراهمشده یا نه»شان مقید شوند.
وقتی تابعی با پارامترهای کلیدواژه فراخوانی میشود، اسم پارامتر به شکل یک کلیدواژه پیش از مقدار پارامتر میآید. فراخوانیکنندهی تابع میتواند پارامترهای کلیدواژه را به هر ترتیبی مشخص کند.
(defun keyword-parameters (&key x (y 'default) (z nil z-supplied-p))
(list x y (if z-supplied-p (list :z-was-supplied z)
(list :z-was-not-supplied z))))
(keyword-parameters) ; => (NIL DEFAULT (:Z-WAS-NOT-SUPPLIED NIL))
(keyword-parameters :y 5) ; => (NIL 5 (:Z-WAS-NOT-SUPPLIED NIL))
(keyword-parameters :z 10 :x 5) ; => (5 DEFAULT (:Z-WAS-SUPPLIED 10))
هنگام ترکیب پارامترهای اختیاری و کلیدواژه باید دقت کرد، چون ممکن است اسم و آرگومان کلیدواژه توسط پارامترهای اختیاری مصرف شوند:
(defun could-be-confusing (&optional x y &key z) (list x y z))
(could-be-confusing :z 'huh?) ; => (:Z HUH? NIL)
در Common Lisp یک تابع میتواند پارامتری داشته باشد که «باقیماندهی» آرگومانها را پس از پردازش پارامترهای الزامی یا اختیاری در خود نگه میدارد.
این پارامتر با کلیدواژهی &rest در لیست لامبدا مشخص میشود.
اگر همهی آرگومانهای یک تابع توسط انواع دیگر پارامترها مصرف شوند، پارامتر باقیمانده به یک لیست خالی مقید میشود.
اگر آرگومانهای استفادهنشدهای وجود داشته باشد، پارامتر باقیمانده به لیستی از آن آرگومانها مقید میشود.
(defun rest-of-it (req &optional opt &rest rest) (list req opt rest))
(rest-of-it 1) ; => (1 NIL NIL)
(rest-of-it 1 2) ; => (1 2 NIL)
(rest-of-it 1 2 3) ; => (1 2 (3))
(rest-of-it 1 2 3 4 5) ; => (1 2 (3 4 5))
لیلی، فضاییِ لیسپ، قطعاً با کمک شما لازانیای زیادی درست کرده است. او میخواهد با دادن مقداری از غذای باقیمانده از شما تشکر کند. اما اول میخواهد دربارهی یکی از توابعی که در نوشتنش به او کمک کردید بازخوردی بدهد.
در این تمرین چند تابع دیگر مینویسید تا به لیلی در پخت لازانیا کمک کنید و همچنین به او کمک کنید غذای باقیمانده را با شما تقسیم کند.
شش کار پیش رو دارید: سه کار دربارهی بهبود توابع کنونی پخت لازانیا و سه کار دربارهی تقسیم غذای باقیمانده.
preparation-time-in-minutes را سادهتر کنیدلیلی میگوید استفاده از preparation-time-in-minutes کمی برایش دشوار بوده، چون باید پیش از استفاده از تابع همهی لایهها را میشمرد. لیلی میپرسد آیا میتوانید نسخهای از تابع بسازید که لایهها (هر تعداد از آنها) را به عنوان «آرگومان» بگیرد و سپس زمان آمادهسازی را بر اساس تعداد لایهها محاسبه کند؟ یادآوری میکنیم که آماده کردن هر لایه ۱۹ دقیقه طول میکشد.
(preparation-time 'sauce 'cheese 'right-handed-macaroni 'cheese 'sauce
'left-handed-macaroni 'sauce 'sauce 'cheese 'cheese)
;; => 190 (because there are 10 layers.)
اگرچه والدین لیلی همیشه لازانیای خودشان را ۳۳۷ دقیقه میپختند، لیلی میپذیرد که فضاییهای لیسپ دیگری هم هستند که لازانیایشان را بیشتر یا کمتر میپزند.
پس خوب است که remaining-minutes-in-oven از expected-minutes-in-oven استفاده نکند و بهجایش بتواند یک پارامتر اختیاری بگیرد که مشخص کند زمان باید طولانیتر، خیلی طولانی، کوتاهتر یا خیلی کوتاه باشد. طولانیتر یا کوتاهتر یعنی ۱۰۰ به زمان پیشفرض ۳۳۷ اضافه یا از آن کم شود. خیلی طولانی یا خیلی کوتاه یعنی ۲۰۰ اضافه یا کم شود. اگر پارامتری داده نشود، یا آرگومان «normal» باشد، باید مقدار پیشفرض ۳۳۷ برگردانده شود.
(remaining-minutes-in-oven) ;; => 337
(remaining-minutes-in-oven :normal) ;; => 337
(remaining-minutes-in-oven :shorter) ;; => 237
(remaining-minutes-in-oven :very-short) ;; => 137
(remaining-minutes-in-oven :longer) ;; => 437
(remaining-minutes-in-oven :very-long) ;; => 537
درست بعد از اینکه آن تغییر را تمام کردید، لیلی به یاد میآورد که فضاییهای لیسپی هم هستند که زمان پخت واقعاً کوتاه را دوست دارند. عملاً میخواهند لازانیا خام بماند. پس حالا تابع باید اگر آرگومان NIL بود ۰ برگرداند و در بقیهی موارد همان مقادیر کار قبلی را.
(remaining-minutes-in-oven) ;; => 337
(remaining-minutes-in-oven nil) ;; => 0
حالا که به بهبود توابع پخت لازانیا کمک کردهاید، کلی لازانیا داریم! لیلی میخواهد در ازای همهی کمکهایتان، غذای باقیمانده را با شما تقسیم کند. باید تابعی بنویسید که سه پارامتر بگیرد: مقدار کل غذای باقیمانده، تعداد ظرفهایی که لیلی برای باقیماندهها در کابینتهایش پیدا کرده و همین تعداد برای شما. چون گشتن برای ظرفهای باقیمانده ممکن است زمانهای متفاوتی ببرد و مقدار دقیق غذای باقیمانده ممکن است همان موقعی مشخص شود که شما و لیلی مشغول گشتن در کابینتها هستید، هر دو توافق میکنید که تابع آرگومانها را به هر ترتیبی بپذیرد، تا هر عددی که مشخص شد همان لحظه آن را بنویسید. وقتی هر سه عدد را داشتید، کافی است پرانتز فراخوانی تابع را ببندید و آن را ارزیابی کنید.
تابع باید مقدار غذای باقیماندهای را برگرداند که باقی میماند، یعنی تفاوت بین وزن غذای باقیمانده و تعداد کل ظرفهایی که شما و لیلی پیدا کردهاید.
(split-leftovers :weight 20 :human 10 :alien 5) ;; => 5
(split-leftovers :weight 20 :alien 10 :human 2) ;; => 8
(split-leftovers :alien 12 :weight 20 :human 4) ;; => 4
پس از چند بار رفتوآمد به کابینتهای یکدیگر، هر دو توافق میکنید که خوب است اگر تابع فقط فرض کند که شما یا لیلی... مثلاً ۱۰ ظرف دارید، در صورتی که این مقدار مشخص نشده باشد.
(split-leftovers :weight 20 :human 5) ;; => 5
(split-leftovers :weight 20 :alien 5) ;; => 5
(split-leftovers :weight 20) ;; => 0
حتی بهتر، تابع میتواند فرض کند که غذای باقیمانده بهاندازهی کافی هست تا در ظرفها جا شود. پس اگر وزن داده نشود، تابع باید :just-split-it را برگرداند. اما اگر وزن داده شود و nil باشد، باید :looks-like-someone-was-hungry را برگرداند.
(split-leftovers :human 5 :alien 5) ;; => :JUST-SPLIT-IT
(split-leftovers :weight NIL :human 5 :alien 5) ;; => :LOOKS-LIKE-SOMEONE-WAS-HUNGRY
در Exercism ثبتنام کنید تا Common Lisp را همراه با 27 مفهوم92 تمرین و مربیگری انسانی واقعی یاد بگیرید و در آن استاد شوید، همهی اینها رایگان.