The with-branching Reference Manual

Table of Contents

Next: , Previous: , Up: (dir)   [Contents][Index]

The with-branching Reference Manual

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.


Next: , Previous: , Up: Top   [Contents][Index]

1 Introduction

WITH-BRANCHING

This is an implementation of macroexpand-time branching in portable Common Lisp.

The main use case is to avoid closing over variables for performance.

Find it in Serapeum!

This library is now also available as a part of Serapeum with a few name differences:

Manual

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.

Bypassing the macroexpand-time branching

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:

Exceptional situations

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.

Testing

(asdf:test-system :with-branching)

Shorter symbol names

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.

Licens


Next: , Previous: , Up: Top   [Contents][Index]

2 Systems

The main system appears first, followed by any subsystem dependency.


Previous: , Up: Systems   [Contents][Index]

2.1 with-branching

Author

Michał "phoe" Herda <phoe@disroot.org>

License

MIT

Description

An implementation of macroexpand-time conditionalization

Version

0.0.1

Dependencies
Source

with-branching.asd (file)

Component

with-branching.lisp (file)


Next: , Previous: , Up: Top   [Contents][Index]

3 Files

Files are sorted by type and then listed depth-first from the systems components trees.


Previous: , Up: Files   [Contents][Index]

3.1 Lisp


Next: , Previous: , Up: Lisp files   [Contents][Index]

3.1.1 with-branching.asd

Location

with-branching.asd

Systems

with-branching (system)


Previous: , Up: Lisp files   [Contents][Index]

3.1.2 with-branching/with-branching.lisp

Parent

with-branching (system)

Location

with-branching.lisp

Packages

with-branching

Exported Definitions
Internal Definitions

Next: , Previous: , Up: Top   [Contents][Index]

4 Packages

Packages are listed by definition order.


Previous: , Up: Packages   [Contents][Index]

4.1 with-branching

Source

with-branching.lisp (file)

Use List

common-lisp

Exported Definitions
Internal Definitions

Next: , Previous: , Up: Top   [Contents][Index]

5 Definitions

Definitions are sorted by export status, category, package, and then by lexicographic order.


Next: , Previous: , Up: Definitions   [Contents][Index]

5.1 Exported definitions


Next: , Previous: , Up: Exported definitions   [Contents][Index]

5.1.1 Special variables

Special Variable: *branch-bypass*

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

Package

with-branching

Source

with-branching.lisp (file)


Previous: , Up: Exported definitions   [Contents][Index]

5.1.2 Macros

Macro: branch-if BRANCH THEN &optional ELSE

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.

Package

with-branching

Source

with-branching.lisp (file)

Macro: branch-unless BRANCH &body BODY

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.

Package

with-branching

Source

with-branching.lisp (file)

Macro: branch-when BRANCH &body BODY

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.

Package

with-branching

Source

with-branching.lisp (file)

Macro: with-branching (&rest BRANCHES) &body BODY

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

Package

with-branching

Source

with-branching.lisp (file)


Previous: , Up: Definitions   [Contents][Index]

5.2 Internal definitions


Next: , Previous: , Up: Internal definitions   [Contents][Index]

5.2.1 Symbol macros

Symbol Macro: %all-branches%
Package

with-branching

Source

with-branching.lisp (file)

Expansion

nil

Symbol Macro: %in-branching%
Package

with-branching

Source

with-branching.lisp (file)

Expansion

nil


Next: , Previous: , Up: Internal definitions   [Contents][Index]

5.2.2 Macros

Macro: %with-branching (&rest BRANCHES) &body BODY
Package

with-branching

Source

with-branching.lisp (file)


Previous: , Up: Internal definitions   [Contents][Index]

5.2.3 Functions

Function: conditional-error NAME
Package

with-branching

Source

with-branching.lisp (file)

Function: missing-branch NAME
Package

with-branching

Source

with-branching.lisp (file)


Previous: , Up: Top   [Contents][Index]

Appendix A Indexes


Next: , Previous: , Up: Indexes   [Contents][Index]

A.1 Concepts

Jump to:   F   L   W  
Index Entry  Section

F
File, Lisp, with-branching.asd: The with-branching․asd file
File, Lisp, with-branching/with-branching.lisp: The with-branching/with-branching․lisp file

L
Lisp File, with-branching.asd: The with-branching․asd file
Lisp File, with-branching/with-branching.lisp: The with-branching/with-branching․lisp file

W
with-branching.asd: The with-branching․asd file
with-branching/with-branching.lisp: The with-branching/with-branching․lisp file

Jump to:   F   L   W  

Next: , Previous: , Up: Indexes   [Contents][Index]

A.2 Functions

Jump to:   %  
B   C   F   M   W  
Index Entry  Section

%
%with-branching: Internal macros

B
branch-if: Exported macros
branch-unless: Exported macros
branch-when: Exported macros

C
conditional-error: Internal functions

F
Function, conditional-error: Internal functions
Function, missing-branch: Internal functions

M
Macro, %with-branching: Internal macros
Macro, branch-if: Exported macros
Macro, branch-unless: Exported macros
Macro, branch-when: Exported macros
Macro, with-branching: Exported macros
missing-branch: Internal functions

W
with-branching: Exported macros

Jump to:   %  
B   C   F   M   W  

Next: , Previous: , Up: Indexes   [Contents][Index]

A.3 Variables

Jump to:   %   *  
S  
Index Entry  Section

%
%all-branches%: Internal symbol macros
%in-branching%: Internal symbol macros

*
*branch-bypass*: Exported special variables

S
Special Variable, *branch-bypass*: Exported special variables
Symbol Macro, %all-branches%: Internal symbol macros
Symbol Macro, %in-branching%: Internal symbol macros

Jump to:   %   *  
S  

Previous: , Up: Indexes   [Contents][Index]

A.4 Data types

Jump to:   P   S   W  
Index Entry  Section

P
Package, with-branching: The with-branching package

S
System, with-branching: The with-branching system

W
with-branching: The with-branching system
with-branching: The with-branching package

Jump to:   P   S   W