Two Fer in Common-lisp: Complete Solution & Deep Dive Guide

a close up of a computer screen with code on it

Mastering Optional Parameters in Common Lisp: The 'Two Fer' Deep Dive

The 'Two Fer' problem in Common Lisp is a foundational exercise for mastering optional function parameters and default values. It involves creating a function that returns "One for [name], one for me," dynamically using a provided name or defaulting to "you" when no name is supplied.

Have you ever been tasked with building a feature that needs to personalize a message? Sometimes you have a user's name, but other times you don't. You're left with a choice: write two separate pieces of logic, or create one elegant, flexible function that handles both cases gracefully. This is a common challenge in software development, and Common Lisp provides a powerful and expressive solution.

This is the core idea behind the "Two Fer" challenge from the exclusive kodikra.com Common Lisp learning path. It uses a simple, relatable story about sharing cookies to teach one of the most practical concepts in programming: optional arguments. In this deep dive, we'll dissect this problem, explore idiomatic Common Lisp solutions, and understand why this small exercise is a giant leap towards writing robust, professional code.


What Exactly is the "Two Fer" Challenge?

Before we dive into the code, let's understand the problem's context. The name "Two Fer" is a colloquialism for "two for one." Imagine a bakery has a special offer: buy one cookie, get a second one free. You, being the generous person you are, decide to give the extra cookie to someone.

The task is to write a program that determines what you say when you give away that extra cookie. The logic is simple:

  • If you know the person's name, you should say: "One for [Name], one for me."
  • If you do not know their name, you should simply refer to them as "you": "One for you, one for me."

This translates into a clear technical requirement: create a function that accepts a single, optional string argument (the name) and returns a formatted string based on whether that argument was provided.

Input and Expected Output

To make it crystal clear, here are the expected behaviors:

Input Name Returned Dialogue String
"Alice" "One for Alice, one for me."
"Bohdan" "One for Bohdan, one for me."
(No name provided) "One for you, one for me."
"Zaphod" "One for Zaphod, one for me."

The beauty of this problem lies in its simplicity. It doesn't require complex algorithms or data structures. Instead, it shines a spotlight on a core feature of the Common Lisp language: its incredibly flexible function parameter lists.


Why This is a Cornerstone Module in Learning Common Lisp

On the surface, "Two Fer" seems trivial. But for someone learning Common Lisp, this small module, part of the first kodikra learning roadmap, is packed with fundamental concepts that are used constantly in professional Lisp development. Mastering this challenge means you're getting a handle on:

  • Function Definition with defun: The universal macro for creating named functions, the primary building block of any Lisp program.
  • Powerful Parameter Lists (&optional): This is the star of the show. Lisp's ability to define optional, keyword, and rest parameters in a clean syntax is one of its most celebrated features. "Two Fer" is the perfect introduction to &optional.
  • String Formatting with format: The format function is Lisp's "Swiss Army knife" for string manipulation. It's far more powerful than simple concatenation, and this exercise provides a practical reason to learn its basic usage.
  • Idiomatic Conditional Logic with or: Many languages treat logical operators like or as strictly boolean. In Lisp, they are control flow structures. Understanding how (or a b) returns the first non-nil value is key to writing concise, idiomatic Lisp.
  • The Lisp Package System (defpackage, in-package): The provided solution correctly uses packages to avoid symbol collisions. This introduces you to how Lisp code is organized into modules, a crucial concept for building larger applications.

By solving this one problem, you touch upon function definition, parameter handling, string creation, and logical flow—the absolute bedrock of the language.


How to Deconstruct the Problem: A Step-by-Step Logical Flow

Before writing a single line of code, a good developer thinks through the logic. Let's break down the required steps to solve the "Two Fer" problem. This thought process is applicable to any programming language, but we'll frame it with Lisp in mind.

  1. Define a Function: We know we need a reusable piece of logic, so we'll start by defining a function. Let's call it twofer.
  2. Handle the Input: The function needs to accept a name. However, the name might not always be provided. This is the key insight: the parameter must be optional.
  3. Implement the Core Logic: Inside the function, we need a decision point. We must check: "Did we receive a name, or was the function called without one?"
  4. Choose the Value: Based on the check in the previous step, we select the value to be inserted into our sentence. If a name was given, we use that name. If not, we use the default string "you".
  5. Construct the Output String: Once we have the correct value (either the name or "you"), we need to embed it into the template sentence: "One for ____, one for me."
  6. Return the Result: The function's final job is to return the newly constructed string.

This entire process can be visualized with a simple flow diagram.

ASCII Art Diagram: The `twofer` Function Logic

    ● Start: `twofer` function is called
    │
    ▼
  ┌───────────────────────────┐
  │ Receive optional `name`   │
  │ argument                  │
  └────────────┬──────────────┘
               │
               ▼
    ◆ Was a `name` provided?
   ╱           (Is `name` non-nil?)
  ╱                             ╲
Yes ────────────┐                ┌─────────── No
                │                │
                ▼                ▼
  ┌──────────────────┐      ┌─────────────────┐
  │ Use provided `name`│      │ Use default "you" │
  └──────────────────┘      └─────────────────┘
                │                │
                └────────┬───────┘
                         │
                         ▼
           ┌───────────────────────────────┐
           │ Inject value into string      │
           │ "One for ____, one for me."   │
           └────────────┬──────────────────┘
                        │
                        ▼
                  ● End: Return final string

This diagram clarifies our plan. Now, let's translate this logic into actual Common Lisp code.


The Common Lisp Solution: A Detailed Code Walkthrough

The solution provided in the kodikra.com module is concise, effective, and highly idiomatic. It's a perfect example of Lisp's expressive power. Let's dissect it line by line to understand every component.

Here is the complete code snippet:

(in-package :cl-user)

(defpackage :two-fer
  (:use :cl)
  (:export :twofer))

(in-package :two-fer)

(defun twofer (&optional name)
  (format nil "One for ~a, one for me." (or name "you")))

It might look small, but there's a lot of power packed into these few lines. Let's break it down.

Part 1: The Package Definitions

(in-package :cl-user)
(defpackage :two-fer
  (:use :cl)
  (:export :twofer))
(in-package :two-fer)
  • (in-package :cl-user): This line ensures we are in the default user package before defining a new one. It's good practice to start from a known state.
  • (defpackage :two-fer ...): This defines a new package named two-fer. A package in Common Lisp is a namespace; it prevents function and variable names from one part of a program from clashing with those in another. It's similar to modules in Python or packages in Java.
  • (:use :cl): This tells our new package to inherit all the standard symbols (like defun, format, or, etc.) from the main :common-lisp package, which is aliased as :cl. Without this, we'd have to write cl:defun everywhere!
  • (:export :twofer): This is crucial. It makes the symbol twofer public. This means that other packages can import and use our twofer function. Anything not exported is considered private to the package.
  • (in-package :two-fer): After defining our package, this command switches the current working namespace to :two-fer. All subsequent code (like our defun) will be defined within this package.

Part 2: The Function Definition

(defun twofer (&optional name) ... )
  • defun: This is the macro used to "define a function."
  • twofer: This is the name we've given our function. By Lisp convention, names are typically lowercase and words are separated by hyphens (lisp-case).
  • (&optional name): This is the parameter list and the core of our solution.
    • The &optional lambda list keyword signals that all following parameters are optional.
    • name is our parameter. Because it follows &optional, a caller can invoke (twofer) without any arguments, or (twofer "Alice") with one.
    • When an optional argument is not provided, its value inside the function defaults to nil, which is Lisp's representation for "nothing" or "false."

Part 3: The Function Body - The Magic of `format` and `or`

This single line does all the heavy lifting. Let's split it into its two main components.

The `(or name "you")` Expression

This is perhaps the most "Lispy" part of the solution. In many languages, or is a boolean operator that returns `true` or `false`. In Lisp, or is a macro that evaluates its arguments from left to right and returns the first value that is not nil. It short-circuits, meaning it stops evaluating as soon as it finds a non-nil value.

Let's see how it works in our two scenarios:

  1. When a name is provided: e.g., (twofer "Alice"). Inside the function, the variable name holds the value "Alice". The expression becomes (or "Alice" "you"). Since "Alice" is not nil, or immediately stops and returns "Alice".
  2. When no name is provided: e.g., (twofer). Inside the function, the optional parameter name defaults to nil. The expression becomes (or nil "you"). Since the first argument is nil, or proceeds to the next argument, "you". This is not nil, so or returns "you".

This is a powerful and common idiom in Lisp for providing default values.

ASCII Art Diagram: The `(or ...)` Logic Flow

    ● Start: `or` macro evaluates its arguments
    │
    ├─> Evaluate first argument: `name`
    │
    ▼
  ◆ Is `name` a non-`nil` value?
  ╱         (e.g., a string was passed)
 ╱                                    ╲
Yes ────────────────┐                  ┌────────── No (`name` is `nil`)
                    │                  │
                    ▼                  ▼
  ┌────────────────────────┐  ┌─> Evaluate second argument: "you"
  │ `or` stops evaluating. │  │
  │ Returns the value of   │  │
  │ `name` immediately.    │  │
  └────────────────────────┘  │
                    │         │
                    └─────────┼──> ● `or` returns the value "you"
                              │
                              ▼
                        ● `or` returns the value of `name`

The `format` Function

The result of our (or ...) expression is then passed to the format function.

  • format is Lisp's primary tool for building strings.
  • The first argument, nil, is the destination. When the destination is nil, format doesn't print the string to the console; instead, it returns the finished string as its result. This is exactly what we need.
  • The second argument is the control string, or template: "One for ~a, one for me.".
  • ~a is a format directive. It's a placeholder that means "take the next argument and render it in an aesthetic, human-readable way."
  • The final argument, (or name "you"), provides the value that will replace ~a.

Putting it all together, the function dynamically determines the correct name, injects it into the template string, and returns the final result. Elegant and efficient.


Best Practices & Alternative Implementations

The (or name "you") idiom is clever and classic. However, modern Common Lisp offers an even clearer way to specify default values for optional parameters, which is often preferred for readability.

The Modern Approach: Default Value in Parameter List

You can provide a default value directly within the &optional declaration. This is considered a best practice because it co-locates the parameter with its default, making the function's signature self-documenting.

Here's how the function would look using this style:

(defun twofer (&optional (name "you"))
  (format nil "One for ~a, one for me." name))

Let's analyze the change:

  • (&optional (name "you")): This syntax declares name as an optional parameter. If the caller does not provide a value for name, it will automatically be assigned the default value "you".
  • The function body is now simpler. We no longer need the or expression because we are guaranteed that name will always hold a string value (either the one passed in or the default). We can just pass name directly to format.

Comparison of Approaches

Both solutions are correct and functionally identical. The choice between them is a matter of style and clarity.

Approach Pros Cons
Idiomatic `or`
(or name "you")
- Excellent demonstration of or as a control structure, not just a boolean operator.
- A common pattern seen in older, classic Lisp code.
- The default value is separated from the parameter definition, which can be slightly less readable.
- A beginner might not immediately understand the non-boolean nature of or.
Modern Default Value
(&optional (name "you"))
- Extremely clear and self-documenting. The function signature tells you everything.
- Co-locates the parameter and its default, improving code maintainability.
- Widely considered the modern best practice for this scenario.
- Hides the underlying logic of how defaults are handled, which the or version explicitly demonstrates.

For new projects and for clarity, the second approach—providing the default in the parameter list—is generally recommended. However, understanding the or idiom is crucial for reading and working with existing Lisp codebases.


Frequently Asked Questions (FAQ)

What exactly is &optional in a Common Lisp function?

&optional is a "lambda list keyword." It's a special symbol used in a function's parameter list to indicate that any parameters defined after it are not required. When a caller omits an optional argument, its value inside the function defaults to nil, unless a different default is specified, like (&optional (param "default")).

Why use (format nil "...") instead of a simpler function like concatenate?

While you could use (concatenate 'string "One for " (or name "you") ", one for me."), the format function is generally more readable and powerful, especially for complex strings. The control string acts as a template, making the overall structure of the output clearer than piecing together multiple string fragments. For simple cases, concatenate is fine, but format is the idiomatic and scalable choice.

What does (or name "you") actually do?

It's a control flow macro. It evaluates its arguments from left to right. It returns the value of the first argument it finds that is not nil. If name has a value (e.g., a string), it returns name. If name is nil (because it wasn't provided), it moves on and returns the next value, "you".

Is Common Lisp case-sensitive?

By default, the Common Lisp reader converts all unquoted symbols to uppercase. So, (defun twofer ...) is read and stored internally as (DEFUN TWOFER ...). This means you can call it as (twofer), (TwoFer), or (TWOFER) and it will work the same. However, string literals like "Alice" and "you" are case-sensitive because their contents are not interpreted as symbols.

How do I run this Common Lisp code?

The best way to run Common Lisp is with an interactive environment called a REPL (Read-Eval-Print Loop). You can install a Common Lisp implementation like SBCL (Steel Bank Common Lisp), then start it from your terminal by typing sbcl. You can then paste the code directly into the REPL or load it from a file using (load "your-file.lisp"). Afterwards, you can test the function by typing (two-fer:twofer "World") or (two-fer:twofer).

What's the difference between ~a and ~s in the format function?

Both are format directives for printing Lisp objects. ~a (Aesthetic) prints the object in a human-readable way. For a string, it prints the contents of the string without quotes. ~s (Standard) prints the object in a way that the Lisp reader can understand it. For a string, it would include the double quotes, e.g., "Alice" instead of just Alice. For this exercise, ~a is the correct choice.

Can a function have more than one optional parameter?

Yes, absolutely. You can list as many as you need after &optional. For example: (defun setup-server (&optional (port 8080) (host "localhost"))). You can call this with zero, one, or two arguments: (setup-server), (setup-server 9000), or (setup-server 9000 "example.com").


Conclusion: More Than Just a String

The "Two Fer" problem is a perfect example of an ideal learning exercise. It's simple to understand but requires a surprisingly deep dive into the language's core features to solve elegantly. By completing this kodikra module, you've gained hands-on experience with function definitions, the Lisp package system, powerful string formatting, and, most importantly, the flexible parameter lists that make Common Lisp so expressive.

The ability to write functions that can handle a variable number of arguments with sensible defaults is not just an academic trick; it's a fundamental skill for building robust, reusable, and clean APIs. The patterns you've learned here—especially using &optional with default values—will appear time and time again in your journey as a Lisp developer.

Disclaimer: The code and concepts discussed are based on the ANSI Common Lisp standard. Implementations like SBCL 2.4+ or CCL 1.12+ are fully compatible. The principles of optional parameters are timeless and a core part of the language's design.

Ready to continue your journey? Explore the complete Common Lisp learning roadmap or dive deeper into the language with our comprehensive Common Lisp guides.


Published by Kodikra — Your trusted Common-lisp learning resource.