# Guile Style Guide for Safsaf This guide draws on [Riastradh's Lisp Style Rules](https://mumble.net/~campbell/scheme/style.txt), the [Guix Coding Style](https://guix.gnu.org/manual/1.5.0/en/html_node/Coding-Style.html). --- ## Formatting ### Indentation Use **2-space indentation**, no tabs. When a subform follows the operator on the same line, align subsequent subforms to that column. When the first subform is on the next line, align it with the operator. Special forms (`define`, `let`, `lambda`, `if`, `cond`, `match`, etc.) follow standard Scheme indentation. ### Line Length Do not exceed **80 columns**. ### Parentheses Never place closing parentheses on their own line. Do not put spaces after opening parentheses or before closing ones. ### Blank Lines Separate top-level forms with a single blank line. Do not place blank lines in procedure bodies except to separate internal definitions from the body. ### Square Brackets Do not use square brackets. They are non-standard and non-portable. ## Naming Write names with **English words separated by hyphens**. No underscores, camelCase, or abbreviations. - **`?` (predicates):** Boolean-returning questions. E.g., `route?`, `logged-in?`. - **`!` (mutation):** Procedures whose primary purpose is destructive update. E.g., `set-route-handler!`. Do not append to every procedure with side effects. - **`%` (private):** Module-private bindings. E.g., `%make-route`, `%email-rx`. - **`*` (variants):** Variations on a theme (`let*`, `define*`). ### Records ```scheme (define-record-type (%make-route method pattern handler name) ; internal constructor route? ; predicate (method route-method) ; accessor (handler route-handler set-route-handler!)) ; accessor + setter ``` - `make-foo` for public constructors, `%make-foo` for raw constructors. - `foo?` for predicates, `foo-field` for accessors, `set-foo-field!` for setters. ### Parameters and Dynamic State - Guile parameters use the **`current-`** prefix: `current-session`, `current-csrf-token`. - **`with-`** establishes dynamic state and calls a thunk. - **`call-with-`** calls a procedure with arguments, managing resources or continuations. ### Local Variables Use meaningful names. Single-letter names only for unambiguous index variables in tight loops. ## Comments ```scheme ;;;; file-heading.scm — File Heading ;;; ;;; Section Heading ;;; ;;; Top-level explanatory comment. (define (fnord zarquon) ;; Fragment comment before code. (quux zot mumble ;margin note frotz)) ``` - **`;;;;`** — File headings. - **`;;;`** — Section headings (use the sandwich: `;;;` / `;;; Title` / `;;;`) and top-level explanations. - **`;;`** — Fragment comments, before the code they describe. - **`;`** — Margin comments, after code on the same line. Write comments only where the code cannot explain itself. ## Docstrings All public procedures must carry a docstring. Place it as the first expression after the parameter list. Describe what the procedure does, its parameters, and return values: ```scheme (define* (route method pattern handler #:key (name #f)) "Create a route. METHOD is a symbol, list of symbols, or '* for any. HANDLER is a procedure (request body-port) -> (values response body). NAME is an optional symbol used for reverse routing with path-for." ...) ``` For parameters, use `set-procedure-property!` with `'documentation`. ## Module Definitions Use `define-module` with `#:use-module` and `#:export`. Group imports: 1. **Standard library** — `(ice-9 ...)`, `(web ...)` 2. **SRFIs** — `(srfi srfi-1)`, `(srfi srfi-9)`, etc. 3. **External dependencies** — `(knots ...)`, `(webutils ...)`, `(json ...)` 4. **Internal modules** — `(safsaf ...)` Prefer `#:use-module`; use `#:autoload` for heavy or circular deps. Standalone scripts may use `use-modules` instead of `define-module`. ## Procedures - No more than **four positional parameters**. Use keyword arguments (via `define*`) beyond that. - Keep procedures under roughly **21 lines** (excluding docstring). Break long procedures into meaningfully named helpers. - Prefer **purely functional** code. Use mutation only for I/O, performance, and low-level utilities. - Avoid point-free style and functional combinators. Use explicit `lambda`. Reserve `compose` for cases where composition is genuinely the idea being expressed. ## Data Types - Prefer **records** (`define-record-type` from `(srfi srfi-9)`) over ad-hoc lists. Do not browse data with `car`/`cdr`/`cadr`. - Do not export record type descriptors (e.g., ``). Export only predicates, constructors, and accessors. - Use `(ice-9 match)` for pattern matching. - Use **alists** for lightweight key-value data (route params, headers, config). ## Multiple Return Values Use `values` for multiple return values. Prefer `(srfi srfi-71)` extended `let` over `(srfi srfi-11)` `let-values`: ```scheme (let ((response body (handler request body-port))) (values response body)) ``` ## Error Handling Use `(ice-9 exceptions)` functionality. ## File Organization - Keep files under **512 lines**. Do not exceed 1024. - Minimize module dependencies. - Structure files with `;;;` section headings. ## Testing Tests use `define-suite`, `suite`, `test`, and `is`. Keep test files under `tests/`.