Common Lisp Hash-Dot #.读取宏的工作原理、功能及应用场景咨询
#.) Reader Macro in Common Lisp Hey there! Let's dig into the #. reader macro—this is one of those handy but under-documented features in Common Lisp that lets you do read-time evaluation, and it's super useful once you wrap your head around it.
What It Does, In Plain Terms
The #. macro tells the Common Lisp reader: "Stop what you're doing, evaluate this expression right now (while you're reading the code), and replace the #. plus expression with the result."
Normally, Lisp reads your code into an abstract syntax tree (AST) first, then executes it later at runtime. With #., you're jumping the gun—running code during the reading phase itself, and inserting the output directly into your code structure before execution even starts.
Basic Examples
Let's start with simple stuff to see it in action:
1. Precomputing Values
;; Instead of calculating 2+3 at runtime, it's done when the code is read (defparameter *five* #.(+ 2 3)) ;; When you inspect *five*, it's already 5—no calculation happens when you use it later
2. Capturing Dynamic State At Read Time
;; This captures the current universal time (a timestamp) when the code is read, not when it's run (defparameter *load-time* #.(get-universal-time)) ;; If you run this code, wait 10 seconds, then check *load-time*, it won't update—it's fixed at read time
3. Building Complex Structures Upfront
;; Creates a list of even numbers from 2 to 10 during reading (defparameter *even-nums* #.(loop for i from 1 to 5 collect (* i 2))) ;; *even-nums* will be '(2 4 6 8 10) as soon as the code is read
How It Works Under The Hood
When the Lisp reader encounters the sequence #., it:
- Reads the next complete Lisp expression (using the same rules it uses for reading any code).
- Evaluates that expression in the read-time environment—this is crucial because the environment at read time might differ from runtime (e.g., some variables or functions might not be defined yet).
- Takes the result of that evaluation and treats it as the object that was "read" in place of the
#.plus expression. - Continues reading the rest of the code as normal.
Practical Use Cases
Now that you know what it does, here are some scenarios where #. shines:
- Precomputing Constant Values: For things like file paths, mathematical constants, or fixed lookup tables that don't change. Instead of recalculating them every time your code runs, you compute them once at read time.
;; Resolves the config path once when the code is loaded (defparameter *app-config-path* #.(merge-pathnames "config.lisp" (user-homedir-pathname))) - Embedding External Data: Load configuration files, static data, or generated code directly into your program during the read phase.
;; Reads a config form from a file and embeds it into the code (defparameter *app-config* #.(uiop:read-file-form "config.lisp")) - Optimizing Performance: If you have a complex expression that's used repeatedly but never changes, precomputing it with
#.saves runtime overhead.
Important Caveats
Before you go wild with #., keep these in mind:
- Security Risk: Never read untrusted code that uses
#.—it can execute arbitrary code during the read phase, which is a huge injection hazard. - Debugging Headaches: Errors in the
#.expression happen at read time, not runtime. This can make them trickier to track down, especially if you're loading code from files. - Environment Differences: The read-time environment might not have access to variables or functions defined later in your code. If you try to reference something that hasn't been loaded yet, you'll get an error.
- Portability: While
#.is part of the Common Lisp standard, some restricted environments (like sandboxes) might disable read-time evaluation for safety.
内容的提问来源于stack exchange,提问作者MadPhysicist

