Parameter lists in Common Lisp, called lambda lists,
are written in their own mini-language, making it convenient to write
functions with a flexible call interface. A lambda list can specify
optional parameters, optionally with default arguments, named
(keyword) parameters, and whether or not the function is
variadic. It’s something I miss a lot when using other languages,
especially JavaScript.

Common Lisp Parameters

Here some some examples. This function, foo, has three required
parameters, a, b, and c.

(defunfoo(abc)...)

To make b and c optional, place them after the symbol &optional,

(defunfoo(a&optionalbc)...)

If second and third arguments are not provided, b and c will be
bound to nil. To provide a default argument, put that parameter
inside a list. Below, when a third argument is not provided, c will
be bound to "bar".

(defunfoo(a&optionalb(c"bar"))...)

To write a function that accepts any number of arguments, use &rest
followed by the parameter to hold the list of the remaining
arguments. Below, args will be a list of all arguments after the
third. Note how this can be combined with &optional.

(defunfoo(a&optionalbc&restargs)...)

Often, the position of a parameter may be hard to remember or read,
especially if there are many parameters. It may be more convenient to
name them with &key. Below, the function has three named parameters,
specified at the call site using keywords — special symbols from
the keyword package that always evaluate to themselves.

(defunfoo(&keyabc)...)(foo:b"world":a"hello")

Like optional parameters, when a parameter is not provided it is bound
to nil. In the same way, it can be given a default argument.

(defunfoo(&key(a"hello")(b"world")c)...)

&key can be combined with &optional and &rest. However, the
&rest argument will be filled with all of key-value pairs, so it’s
generally not useful to use them together.

Lambda lists are not exclusive to defun and can be used in any place
that needs to receive values in parameters, such as flet (function
let), defmethod, and so on.

Clojure Parameters

Clojure forgoes these complex lambda lists in preference for
overloading by arity. When a function is being defined, multiple
functions of different arities can be defined at once. This makes for
optional parameters. Note how this leaves no room for a default
argument of nil for unspecified optional arguments.

Here, b is an optional parameter for foo, defaulting to "bar"
when not provided by the caller. The first definition has an arity of
one and it calls the second definition with the optional argument
filled in.

(defnfoo([a](fooa"bar"))([ab]...))

Variadic functions are specified with &, similar to &rest in
Common Lisp. Below, xs is a sequence of all of the arguments
provided after the first.

(defnfoo[x&xs]...)

As far as parameters are concerned, this is all Clojure
has. However, Clojure’s parameter specification is actually more
flexible than Common Lisp’s lambda lists in two important ways. One is
that parameter position can vary with the number of provided
arguments. The Clojure core functions use this a lot (ex.
reduce).

The following in Common Lisp would require manually parsing the
parameters on some level. The last parameter can be either second or
third depending on whether a middle name was provided.

That covers optional parameters with default arguments and variadic
functions. What about keyword parameters? Well, to cover that we need
to talk about destructuring, which is another way that Clojure
parameters are more powerful than lambda lists.

Destructuring

A powerful Lisp idiom is destructuring bindings. Variables can be
bound to values in a structure by position in the structure. In Common
Lisp there are three macros for making destructuring bindings,
destructuring-bind, loop and with-slots (CLOS).

Below, in the body of the form, a, b, and c are bound to 1, 2,
and 3 respectively. The form (a (b c)) is mapped into the quoted
structure of the same shape to the right.

(destructuring-bind(a(bc))'(1(23))(+a(*bc)));; => 7

Because of Common Lisp’s concept of cons cells, the cdr of a cell
can be bound to a variable if that variable appears in the cdr
position. This is similar to the &rest parameter (and is how Scheme
does variadic functions). I like using this to match the head and tail
of a list,

(destructuring-bind(x.xs)'(12345)(listxxs));; => (1 (2 3 4 5))

Perhaps the neatest use of destructuring is in the loop macro. This
loop walks over a list two at a time, binding a variable to each side
of the pair,

Unfortunately destructuring in Common Lisp is limited to these few
cases, or where ever else you write your own destructuring macros.

Clojure takes destructuring to its logical conclusion: destructuring
can be used any place bindings are established! This includes
parameter lists. It works on any core data structure, not just lists.

Below, I’m doing destructuring inside of a standard let form.

(defngreet-dr[fullname](let[[firstlast](clojure.string/splitfullname#" +")](str"Hello, Dr. "last". ""It's good to see you again, "first".")))(greet-dr"John Doe");; "Hello, Dr. Doe. It's good to see you again, John."

Similarly, I could destructure an argument into my parameters. (Note
the double square brackets.)

(defngreet-dr-2[[firstlast]]...)(greet-dr-2["John""Doe"])

Because hashmaps are a core language feature in Clojure, they can also
be destructured. The syntax is a bit like flipping the hashmap inside
out. The variable is specified, then the key it’s mapped to.

(let[{a:a,b:b}{:a1:b2}](listab));; => (1 2)

When variables and keys have the same name, there’s a shorthand with
:keys.

(let[{:keys[ab]}{:a1:b2}]...)

Variables default to nil when the corresponding key is not in the
map. They can be given default values with :or.

(let[{a:a,b:b:or{a0b0}}{}](listab));; => (0 0)

Now, here’s where it gets really neat. In Common Lisp, the &key part
of a lambda list is a special case. In Clojure it comes for free as
part of destructuring. Just destructure the rest argument!

(defnheight-opinion[name&{height:height}](if-notheight(str"I have no opinion on "name".")(if(<height6)(strname" is short.")(strname" is tall."))))(height-opinion"Chris":height6.25);; => "Chris is tall."

We can still access the entire rest argument at the same time, using
:as, so it covers everything Common Lisp covers.

(defnfoo[&{a:a,b:b:asargs}]args)(foo:b10);; => {:b 10}

(A side note while we’re making comparisons: keywords in Clojure are
not symbols, but rather a whole type of their own.)

Conclusion

Clojure parameter lists are simpler than Common Lisp’s lambda lists
and, thanks to destructuring anywhere, they end up being more
powerful at the same time. It’s a full super set of lambda lists, so
there’s no practical trade-off.