{"id":16410995,"url":"https://github.com/phoe/with-branching","last_synced_at":"2026-03-05T14:05:15.143Z","repository":{"id":111725083,"uuid":"446129131","full_name":"phoe/with-branching","owner":"phoe","description":"An implementation of macroexpand-time conditionalization.","archived":false,"fork":false,"pushed_at":"2023-11-20T13:42:27.000Z","size":35,"stargazers_count":13,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-24T04:23:40.666Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phoe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-09T15:35:41.000Z","updated_at":"2023-12-04T19:11:07.000Z","dependencies_parsed_at":"2024-11-10T00:44:46.954Z","dependency_job_id":"57d75da5-0859-41b1-b1bb-63bf00e08e28","html_url":"https://github.com/phoe/with-branching","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/phoe/with-branching","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phoe%2Fwith-branching","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phoe%2Fwith-branching/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phoe%2Fwith-branching/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phoe%2Fwith-branching/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phoe","download_url":"https://codeload.github.com/phoe/with-branching/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phoe%2Fwith-branching/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30130032,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T12:40:50.676Z","status":"ssl_error","status_checked_at":"2026-03-05T12:39:32.209Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T06:44:20.150Z","updated_at":"2026-03-05T14:05:15.126Z","avatar_url":"https://github.com/phoe.png","language":"Common Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WITH-BRANCHING\n\nThis is an implementation of macroexpand-time branching in portable Common Lisp.\n\nThe main use case is to avoid closing over variables for performance.\n\n## Find it in Serapeum!\n\nThis library [is now also available](https://github.com/ruricolist/serapeum/pull/105)  as a part of [Serapeum](https://github.com/ruricolist/serapeum/) with a few name differences:\n* `WITH-BRANCHING` → `WITH-BOOLEAN`\n* `BRANCH-IF` → `BOOLEAN-IF`\n* `BRANCH-WHEN` → `BOOLEAN-WHEN`\n* `BRANCH-UNLESS` → `BOOLEAN-UNLESS`\n\n## Manual\n\nLet's consider the following function:\n\n```lisp\n(defun make-adder (x \u0026key huge-p)\n  (lambda (y) (+ x y (if huge-p 1000 0))))\n```\n\nThe result of calling `(MAKE-ADDER 10)` closes over `HUGE-P` and makes a runtime check for its value.\n\n```lisp\nCL-USER\u003e (disassemble (make-adder 10))\n; disassembly for (LAMBDA (Y) :IN MAKE-ADDER)\n; Size: 65 bytes. Origin: #x53730938                          ; (LAMBDA (Y) :IN MAKE-ADDER)\n; 38:       488975F8         MOV [RBP-8], RSI\n; 3C:       488BD3           MOV RDX, RBX\n; 3F:       E8EC012DFF       CALL #x52A00B30                  ; GENERIC-+\n; 44:       488B75F8         MOV RSI, [RBP-8]\n; 48:       4881FE17011050   CMP RSI, #x50100117              ; NIL\n; 4F:       BFD0070000       MOV EDI, 2000\n; 54:       B800000000       MOV EAX, 0\n; 59:       480F44F8         CMOVEQ RDI, RAX\n; 5D:       E8CE012DFF       CALL #x52A00B30                  ; GENERIC-+\n; 62:       488BE5           MOV RSP, RBP\n; 65:       F8               CLC\n; 66:       5D               POP RBP\n; 67:       C3               RET\n; 68:       CC10             INT3 16                          ; Invalid argument count trap\n; 6A:       6A20             PUSH 32\n; 6C:       E8FFFA2CFF       CALL #x52A00470                  ; ALLOC-TRAMP\n; 71:       5B               POP RBX\n; 72:       E958FFFFFF       JMP #x537308CF\n; 77:       CC10             INT3 16                          ; Invalid argument count trap\nNIL\n```\n\nIt 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.\n\n```lisp\n(defun make-adder (x \u0026key huge-p)\n  (if huge-p\n      (lambda (y) (+ x y 1000))\n      (lambda (y) (+ x y 0))))\n```\n\nA brief look at the disassembly of this fixed version shows us that we're right:\n\n```lisp\nCL-USER\u003e (disassemble (make-adder 10))\n; disassembly for (LAMBDA (Y) :IN MAKE-ADDER)\n; Size: 21 bytes. Origin: #x53730BC7                          ; (LAMBDA (Y) :IN MAKE-ADDER)\n; C7:       488BD1           MOV RDX, RCX\n; CA:       E861FF2CFF       CALL #x52A00B30                  ; GENERIC-+\n; CF:       31FF             XOR EDI, EDI\n; D1:       E85AFF2CFF       CALL #x52A00B30                  ; GENERIC-+\n; D6:       488BE5           MOV RSP, RBP\n; D9:       F8               CLC\n; DA:       5D               POP RBP\n; DB:       C3               RET\nNIL\n```\n\nStill, 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:\n\n```lisp\n(defun make-adder (x \u0026key huge-p enormous-p humongous-p)\n  (lambda (y) (+ x y\n                 (if huge-p 1000 0)\n                 (if enormous-p 2000 0)\n                 (if humongous-p 3000 0))))\n```\n\nBut it would look like this for the macroexpand-time version:\n\n```lisp\n(defun make-adder (x \u0026key huge-p enormous-p humongous-p)\n  (if huge-p\n      (if enormous-p\n          (if humongous-p\n              (lambda (y) (+ x y 1000 2000 3000))\n              (lambda (y) (+ x y 1000 2000 0)))\n          (if humongous-p\n              (lambda (y) (+ x y 1000 0 3000))\n              (lambda (y) (+ x y 1000 0 0))))\n      (if enormous-p\n          (if humongous-p\n              (lambda (y) (+ x y 0 2000 3000))\n              (lambda (y) (+ x y 0 2000 0)))\n          (if humongous-p\n              (lambda (y) (+ x y 0 0 3000))\n              (lambda (y) (+ x y 0 0 0))))))\n```\n\nThe 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:\n\n```lisp\n(defun make-adder (x \u0026key huge-p enormous-p humongous-p)\n  (with-branching (huge-p enormous-p humongous-p)\n    (lambda (y) (+ x y\n                   (branch-if huge-p 1000 0)\n                   (branch-if enormous-p 2000 0)\n                   (branch-if humongous-p 3000 0)))))\n```\n\nThis code gives us the clarity of runtime-checked version and the performance of a compile-time-checked version (at a cost of increased code size). 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.\n\nThree conditional operators are provided - `BRANCH-IF`, `BRANCH-WHEN`, and `BRANCH-UNLESS`, mimicking the syntax of, respectively, `IF`, `WHEN`, and `UNLESS`.\n\n## Bypassing the macroexpand-time branching\n\nIt 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:\n* `WITH-BRANCHING` expands into a `PROGN` form,\n* `BRANCH-IF` expands into an `IF` form,\n* `BRANCH-WHEN` expands into a `WHEN` form,\n* `BRANCH-UNLESS` expands into an `UNLESS` form.\n\n## Exceptional situations\n\nTrying to use `BRANCH-IF`, `BRANCH-WHEN`, or `BRANCH-UNLESS` outside the lexical environment established by `WITH-BRANCHES` will signal a `PROGRAM-ERROR`.\n\nTrying to use a branch name `BRANCH-IF`, `BRANCH-WHEN`, or `BRANCH-UNLESS` that wasn't declared in `WITH-BRANCHES` will signal a `PROGRAM-ERROR`.\n\n## Testing\n\n`(asdf:test-system :with-branching)`\n\n## Shorter symbol names\n\nIf 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.\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphoe%2Fwith-branching","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphoe%2Fwith-branching","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphoe%2Fwith-branching/lists"}