En Common Lisp, la lista de argumentos de una función se conoce como una 'lista lambda'.
Una lista lambda puede tener argumentos de distintos tipos.
Estos distintos tipos se designan con el uso de 'palabras clave de lista lambda', que siempre empiezan con &.
Los tipos más usados son los argumentos opcionales, de palabra clave y rest.
Todos los parámetros de la lista lambda que vengan después de una palabra clave de lista lambda determinada serán de ese tipo.
Una palabra clave de lista lambda solo se puede usar una vez en una lista lambda.
Las listas lambda también se usan en otras construcciones que veremos más adelante, como la desestructuración y las macros.
En Common Lisp, una función puede tener algunos argumentos que son opcionales.
Estos se designan en la lista lambda con la palabra clave de lista lambda &optional.
Un parámetro se vinculará al valor nil si no se especifica.
Si hay varios parámetros opcionales, se vinculan en orden.
Se pueden especificar valores por defecto para los parámetros opcionales.
Por último, se puede especificar un símbolo para cada parámetro opcional, el cual quedará vinculado a verdadero o falso según si quien llama a la función suministró ese parámetro (a esto se le llama «parámetro 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))
En Common Lisp, una función puede tener parámetros con nombre (a los que se llama «parámetros de palabra clave»).
Estos se designan en la lista lambda con la palabra clave de lista lambda &key.
Los parámetros de palabra clave no son parámetros obligatorios.
Al igual que los parámetros opcionales, se les pueden dar valores por defecto y símbolos a los que vincular su estado de «suministrado o no».
Cuando se llama a una función con parámetros de palabra clave, el nombre del parámetro como palabra clave se usa delante del valor del parámetro. Quien llama a la función puede especificar los parámetros de palabra clave en cualquier orden.
(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))
Hay que tener cuidado al combinar parámetros opcionales y de palabra clave, ya que los parámetros opcionales podrían consumir el nombre de la palabra clave y el argumento:
(defun could-be-confusing (&optional x y &key z) (list x y z))
(could-be-confusing :z 'huh?) ; => (:Z HUH? NIL)
En Common Lisp, una función puede tener un parámetro que contendrá el «resto» de los argumentos después de que se procesen los parámetros obligatorios u opcionales.
Este parámetro se designa con la palabra clave de lista lambda &rest.
Si otros tipos de parámetros usan todos los argumentos de una función, el parámetro rest quedará vinculado a una lista vacía.
Si quedan argumentos sin usar, el parámetro rest quedará vinculado a una lista con esos argumentos.
(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))
Lilly, la extraterrestre de Lisp, definitivamente ha preparado mucha lasaña con tu ayuda. Quiere agradecerte dándote algunas sobras. Pero primero tiene algunos comentarios sobre una de las funciones con las que la ayudaste.
En este ejercicio vas a escribir algunas funciones más para ayudar a Lilly con su cocina de lasaña, además de ayudarla a repartir las sobras contigo.
Tienes seis tareas: tres sobre mejorar las funciones existentes de cocina de lasaña y tres sobre repartir las sobras.
preparation-time-in-minutes sea más fácil de usarLilly dice que le resultó un poco incómodo usar tu preparation-time-in-minutes, ya que tenía que contar todas las capas antes de usar las funciones. Lilly se pregunta si podrías hacer una versión de la función que reciba las capas (cualquier cantidad de ellas) como argumentos y luego calcule el tiempo de preparación según cuántas capas haya. Como recordatorio, preparar una sola capa toma 19 minutos.
(preparation-time 'sauce 'cheese 'right-handed-macaroni 'cheese 'sauce
'left-handed-macaroni 'sauce 'sauce 'cheese 'cheese)
;; => 190 (because there are 10 layers.)
Mientras que las unidades parentales de Lilly siempre cocinaban su lasaña durante 337 minutos, Lilly admite que a otros extraterrestres de Lisp les gusta cocinar su lasaña por más o menos tiempo.
Así que sería bueno que remaining-minutes-in-oven no usara expected-minutes-in-oven, sino que en su lugar pudiera recibir un parámetro opcional para definir si el tiempo debería ser más largo, muy largo, más corto o muy corto. Más largo o más corto significa sumar o restar 100 al tiempo predeterminado de 337. Muy largo o muy corto significa sumar o restar 200. Si no se proporciona ningún parámetro, o el argumento es «normal», debería devolverse el valor predeterminado de 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
Justo después de que terminaste ese cambio, Lilly recuerda que hay algunos extraterrestres de Lisp a los que les gustan los tiempos de cocción realmente cortos. Básicamente la quieren cruda. Así que ahora la función debería devolver 0 si el argumento fue NIL, además de los mismos valores que en la tarea anterior.
(remaining-minutes-in-oven) ;; => 337
(remaining-minutes-in-oven nil) ;; => 0
Ahora que ayudaste a mejorar las funciones de cocina de la lasaña, ¡hay mucha lasaña! Lilly quiere pagarte por toda tu ayuda repartiendo las sobras contigo. Necesitarás escribir una función que reciba tres parámetros: la cantidad total de sobras, el número de recipientes para sobras que Lilly encontró en sus alacenas y el mismo dato para ti. Como buscar recipientes para sobras puede tomar cantidades de tiempo diferentes, y la cantidad exacta de sobras puede determinarse mientras tú y Lilly están buscando en sus alacenas, ambos acuerdan que la función debería recibir los argumentos en cualquier orden, de modo que a medida que se determine cada número lo puedas anotar. Cuando tengas los tres números, puedes simplemente cerrar el paréntesis de la llamada a la función y evaluarla.
La función debería devolver la cantidad de sobras que quedan, es decir, la diferencia entre el peso de las sobras y el número total de recipientes que tú y Lilly encontraron.
(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
Después de unos cuantos viajes de ida y vuelta a sus respectivas alacenas, ambos acuerdan que sería genial si la función pudiera simplemente asumir que tú o Lilly tenían... digamos, 10 recipientes si no se especificaba.
(split-leftovers :weight 20 :human 5) ;; => 5
(split-leftovers :weight 20 :alien 5) ;; => 5
(split-leftovers :weight 20) ;; => 0
Mejor aún, la función podría asumir que hay suficientes sobras para caber en los recipientes. Así que si no se proporciona el peso, la función debería devolver :just-split-it. Sin embargo, si se proporciona el peso y es nil, entonces debería devolver :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
Regístrate en Exercism para aprender y dominar Common Lisp con 27 conceptos92 ejercicios y mentoría humana real, todo gratis.