У Common Lisp аргументний список функції називають «лямбда-списком».
Лямбда-список може містити аргументи різних типів.
Ці різні типи позначають за допомогою «ключових слів лямбда-списку», які всі починаються з &.
Найпоширеніші типи: необовʼязкові аргументи, аргументи-ключі та аргументи залишку.
Кожен параметр у лямбда-списку після певного ключового слова лямбда-списку матиме саме той тип.
Ключове слово лямбда-списку можна використати в лямбда-списку лише один раз.
Лямбда-списки також використовують в інших конструкціях, про які йтиметься далі, наприклад у деструктуризації та макросах.
У 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 було трохи незручно, бо перед викликом функції доводилося перелічувати всі шари. Ліллі цікавиться, чи не могли б ми зробити версію функції, яка приймає шари (будь-яку їхню кількість) як аргументи, а потім обчислює час підготовки залежно від того, скільки було шарів. Нагадаємо: на підготовку одного шару потрібно 19 хвилин.
(preparation-time 'sauce 'cheese 'right-handed-macaroni 'cheese 'sauce
'left-handed-macaroni 'sauce 'sauce 'cheese 'cheese)
;; => 190 (because there are 10 layers.)
Хоча батьківські одиниці Ліллі завжди запікали свою лазанью 337 хвилин, Ліллі визнає, що інші прибульці з Ліспу люблять запікати свою лазанью довше або коротше.
Тож було б добре, якби remaining-minutes-in-oven не використовувала expected-minutes-in-oven, а натомість могла приймати необовʼязковий параметр, який визначає, чи має час бути довшим, дуже довгим, коротшим або дуже коротким. Довший або коротший означає додати або відняти 100 від типового часу 337. Дуже довгий або дуже короткий означає додати або відняти 200. Якщо параметр не передано або аргумент дорівнює «normal», то має повертатися типове значення 337.
(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
Щойно ми завершили цю зміну, Ліллі згадує, що є прибульці з Ліспу, які люблять дуже короткий час приготування. По суті, вони хочуть, щоб вона була сирою. Тож тепер функція має повертати 0, якщо аргументом було 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
Після кількох походів туди й назад до наших відповідних шаф ми з Ліллі погоджуємося, що було б чудово, якби функція могла просто припускати, що в нас або в Ліллі є... скажімо, 10 контейнерів, якщо їх не вказано.
(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 вправи та справжнє наставництво від людей, і все це безкоштовно.