Next: Introduction, Previous: (dir), Up: (dir) [Contents][Index]
This is the with-branching Reference Manual, version 0.0.1, generated automatically by Declt version 3.0 "Montgomery Scott" on Sun May 15 06:27:39 2022 GMT+0.
• Introduction | What with-branching is all about | |
• Systems | The systems documentation | |
• Files | The files documentation | |
• Packages | The packages documentation | |
• Definitions | The symbols documentation | |
• Indexes | Concepts, functions, variables and data types |
This is an implementation of macroexpand-time branching in portable Common Lisp.
The main use case is to avoid closing over variables for performance.
This library is now also available as a part of Serapeum with a few name differences:
WITH-BRANCHING
→ WITH-BOOLEAN
BRANCH-IF
→ BOOLEAN-IF
BRANCH-WHEN
→ BOOLEAN-WHEN
BRANCH-UNLESS
→ BOOLEAN-UNLESS
Let's consider the following function:
(defun make-adder (x &key huge-p)
(lambda (y) (+ x y (if huge-p 1000 0))))
The result of calling (MAKE-ADDER 10)
closes over HUGE-P
and makes a runtime check for its value.
CL-USER> (disassemble (make-adder 10))
; disassembly for (LAMBDA (Y) :IN MAKE-ADDER)
; Size: 65 bytes. Origin: #x53730938 ; (LAMBDA (Y) :IN MAKE-ADDER)
; 38: 488975F8 MOV [RBP-8], RSI
; 3C: 488BD3 MOV RDX, RBX
; 3F: E8EC012DFF CALL #x52A00B30 ; GENERIC-+
; 44: 488B75F8 MOV RSI, [RBP-8]
; 48: 4881FE17011050 CMP RSI, #x50100117 ; NIL
; 4F: BFD0070000 MOV EDI, 2000
; 54: B800000000 MOV EAX, 0
; 59: 480F44F8 CMOVEQ RDI, RAX
; 5D: E8CE012DFF CALL #x52A00B30 ; GENERIC-+
; 62: 488BE5 MOV RSP, RBP
; 65: F8 CLC
; 66: 5D POP RBP
; 67: C3 RET
; 68: CC10 INT3 16 ; Invalid argument count trap
; 6A: 6A20 PUSH 32
; 6C: E8FFFA2CFF CALL #x52A00470 ; ALLOC-TRAMP
; 71: 5B POP RBX
; 72: E958FFFFFF JMP #x537308CF
; 77: CC10 INT3 16 ; Invalid argument count trap
NIL
It would be better for performance if the test was only made once, in MAKE-ADDER
, rather than on every call of the adder closure. MAKE-ADDER
could then return one of two functions depending on whether the check succeeds.
(defun make-adder (x &key huge-p)
(if huge-p
(lambda (y) (+ x y 1000))
(lambda (y) (+ x y 0))))
A brief look at the disassembly of this fixed version shows us that we're right:
CL-USER> (disassemble (make-adder 10))
; disassembly for (LAMBDA (Y) :IN MAKE-ADDER)
; Size: 21 bytes. Origin: #x53730BC7 ; (LAMBDA (Y) :IN MAKE-ADDER)
; C7: 488BD1 MOV RDX, RCX
; CA: E861FF2CFF CALL #x52A00B30 ; GENERIC-+
; CF: 31FF XOR EDI, EDI
; D1: E85AFF2CFF CALL #x52A00B30 ; GENERIC-+
; D6: 488BE5 MOV RSP, RBP
; D9: F8 CLC
; DA: 5D POP RBP
; DB: C3 RET
NIL
Still, with more flags than one, this style of writing code is likely to become unwieldy. For three flags, we would need to write something like this for the runtime version:
(defun make-adder (x &key huge-p enormous-p humongous-p)
(lambda (y) (+ x y
(if huge-p 1000 0)
(if enormous-p 2000 0)
(if humongous-p 3000 0))))
But it would look like this for the macroexpand-time version:
(defun make-adder (x &key huge-p enormous-p humongous-p)
(if huge-p
(if enormous-p
(if humongous-p
(lambda (y) (+ x y 1000 2000 3000))
(lambda (y) (+ x y 1000 2000 0)))
(if humongous-p
(lambda (y) (+ x y 1000 0 3000))
(lambda (y) (+ x y 1000 0 0))))
(if enormous-p
(if humongous-p
(lambda (y) (+ x y 0 2000 3000))
(lambda (y) (+ x y 0 2000 0)))
(if humongous-p
(lambda (y) (+ x y 0 0 3000))
(lambda (y) (+ x y 0 0 0))))))
The total number of combinations for n
boolean flags is 2^n
, making it hard to write and maintain code with so many branches. This is where WITH-BRANCHING
comes into play. Using it, we can write our code in a way that looks similar to the runtime-check version:
(defun make-adder (x &key huge-p enormous-p humongous-p)
(with-branching (huge-p enormous-p humongous-p)
(lambda (y) (+ x y
(branch-if huge-p 1000 0)
(branch-if enormous-p 2000 0)
(branch-if humongous-p 3000 0)))))
This code gives us the clarity of runtime-checked version and the performance of a checked version. A total of eight versions of the body (and therefore, eight possible LAMBDA
forms) are generated. At runtime, only one of them is selected, based on the boolean values of the three flags we provided.
Three conditional operators are provided - BRANCH-IF
, BRANCH-WHEN
, and BRANCH-UNLESS
, mimicking the syntax of, respectively, IF
, WHEN
, and UNLESS
.
It is possible to use the variable *BRANCH-BYPASS*
for bypassing macroexpand-time branching; this is useful e.g. when trying to read the macroexpansions or when debugging. If that variable is set to true, the behavior of the macroexpander is modified:
WITH-BRANCHING
expands into a PROGN
form,BRANCH-IF
expands into an IF
form,BRANCH-WHEN
expands into a WHEN
form,BRANCH-UNLESS
expands into an UNLESS
form.Trying to use BRANCH-IF
, BRANCH-WHEN
, or BRANCH-UNLESS
outside the lexical environment established by WITH-BRANCHES
will signal a PROGRAM-ERROR
.
Trying to use a branch name BRANCH-IF
, BRANCH-WHEN
, or BRANCH-UNLESS
that wasn't declared in WITH-BRANCHES
will signal a PROGRAM-ERROR
.
(asdf:test-system :with-branching)
If you want to use package-local nicknames for the conditional operators, such as W:IF
, W:WHEN
, or W:UNLESS
, while accepting the risk that people reading your code may mistake them for standard Common Lisp operators, (ASDF:LOAD-SYSTEM :WITH-BRANCHING/DANGEROUS)
and depend on the symbols from the WITH-BRANCHING/DANGEROUS
package instead.
Next: Files, Previous: Introduction, Up: Top [Contents][Index]
The main system appears first, followed by any subsystem dependency.
• The with-branching system |
Michał "phoe" Herda <phoe@disroot.org>
MIT
An implementation of macroexpand-time conditionalization
0.0.1
with-branching.asd (file)
with-branching.lisp (file)
Files are sorted by type and then listed depth-first from the systems components trees.
• Lisp files |
• The with-branching.asd file | ||
• The with-branching/with-branching.lisp file |
Next: The with-branching/with-branching․lisp file, Previous: Lisp files, Up: Lisp files [Contents][Index]
with-branching.asd
with-branching (system)
Previous: The with-branching․asd file, Up: Lisp files [Contents][Index]
with-branching (system)
with-branching.lisp
Next: Definitions, Previous: Files, Up: Top [Contents][Index]
Packages are listed by definition order.
• The with-branching package |
with-branching.lisp (file)
common-lisp
Definitions are sorted by export status, category, package, and then by lexicographic order.
• Exported definitions | ||
• Internal definitions |
Next: Internal definitions, Previous: Definitions, Up: Definitions [Contents][Index]
• Exported special variables | ||
• Exported macros |
Next: Exported macros, Previous: Exported definitions, Up: Exported definitions [Contents][Index]
Bypasses macroexpand-time branching. The bypass inhibits all macroexpand-time
branching and instead defers all checks in expanded code to runtime in the
following manner:
* WITH-BRANCHING -> PROGN
* BRANCH-IF -> IF
* BRANCH-WHEN -> WHEN
* BRANCH-UNLESS -> UNLESS
with-branching.lisp (file)
Previous: Exported special variables, Up: Exported definitions [Contents][Index]
Chooses between the forms to include based on whether a macroexpand-time
branch is true. The first argument must be a symbol naming a branch in the
lexically enclosing WITH-BRANCHING form.
It is an error to use this macro outside the lexical environment established by WITH-BRANCHES.
with-branching.lisp (file)
Includes some forms based on whether a macroexpand-time branch is false. The
first argument must be a symbol naming a branch in the lexically enclosing
WITH-BRANCHING form.
It is an error to use this macro outside the lexical environment established by WITH-BRANCHES.
with-branching.lisp (file)
Includes some forms based on whether a macroexpand-time branch is true. The
first argument must be a symbol naming a branch in the lexically enclosing
WITH-BRANCHING form.
It is an error to use this macro outside the lexical environment established by WITH-BRANCHES.
with-branching.lisp (file)
Establishes a lexical environment in which it is possible to use
macroexpand-time branching. Within the lexical scope of
WITH-BRANCHING, it is possible to use BRANCH-IF,
BRANCH-WHEN, and BRANCH-UNLESS to conditionalize whether
some forms are included at compilation time.
The first argument must be a list of symbols which name variables. This macro will expand into a series of conditionals
with-branching.lisp (file)
Previous: Exported definitions, Up: Definitions [Contents][Index]
• Internal symbol macros | ||
• Internal macros | ||
• Internal functions |
Next: Internal macros, Previous: Internal definitions, Up: Internal definitions [Contents][Index]
with-branching.lisp (file)
nil
with-branching.lisp (file)
nil
Next: Internal functions, Previous: Internal symbol macros, Up: Internal definitions [Contents][Index]
with-branching.lisp (file)
Previous: Internal macros, Up: Internal definitions [Contents][Index]
with-branching.lisp (file)
with-branching.lisp (file)
Previous: Definitions, Up: Top [Contents][Index]
• Concept index | ||
• Function index | ||
• Variable index | ||
• Data type index |
Next: Function index, Previous: Indexes, Up: Indexes [Contents][Index]
Jump to: | F L W |
---|
Jump to: | F L W |
---|
Next: Variable index, Previous: Concept index, Up: Indexes [Contents][Index]
Jump to: | %
B C F M W |
---|
Jump to: | %
B C F M W |
---|
Next: Data type index, Previous: Function index, Up: Indexes [Contents][Index]
Jump to: | %
*
S |
---|
Jump to: | %
*
S |
---|
Previous: Variable index, Up: Indexes [Contents][Index]
Jump to: | P S W |
---|
Jump to: | P S W |
---|