Idioms
Common patterns and best practices for idiomatic Sigil code.
Output
Use print/println with format strings, not display/newline:
;; Idiomatic
(println "Hello, ~a!" name)
(println "Got ~a items" (length items))
;; Avoid (R7RS compatibility only)
(display "Hello, ") (display name) (newline)Threading Pipelines
Use -> for data transformation pipelines instead of nested calls:
;; Idiomatic - reads top to bottom
(-> data
(filter positive? _)
(map square _)
(take 10 _))
;; Avoid - reads inside out
(take 10 (map square (filter positive? data)))The _ placeholder marks where the threaded value goes. Use some-> for nil-short-circuiting:
(some-> user
(dict-ref _ email:)
validate-email
send-confirmation) ; stops if any step returns #fPattern Matching
Use match for type/shape dispatch instead of nested cond:
(match cmd
(('add x y) (+ x y))
(('quit) (exit 0))
((? string? s) (parse-string s))
(_ (error "Unknown command")))Use match-lambda for inline match functions:
(map (match-lambda
((name . value) (format "~a=~a" name value)))
alist)Use match-let for destructuring binds:
(match-let (((x y) point)
((w h) size))
(* (+ x w) (+ y h)))Polymorphic Collections
Import (sigil seq) for operations that work on lists, vectors, arrays, and dicts:
(import (sigil seq))
(map square '(1 2 3)) ; => (1 4 9)
(map square #(1 2 3)) ; => #(1 4 9)
(filter even? #(1 2 3 4)) ; => #(2 4)
(fold + 0 '(1 2 3 4)) ; => 10Transducers
For complex transformations, compose transducers:
(import (sigil seq))
(define xform
(comp (filtering even?)
(mapping square)
(taking 5)))
(sequence xform (iota 100)) ; => (0 4 16 36 64)
(into #() xform '(1 2 3 4)) ; => #(4 16)Function Composition
Import (sigil fn) for point-free style:
(import (sigil fn))
(define add1 (partial + 1))
(define process (pipe string-trim string-upcase))
(filter (complement null?) items)Higher-Order Functions
Prefer HOFs over manual recursion:
;; Good
(filter even? numbers)
(map string-upcase names)
(fold + 0 values)
(filter-map maybe-transform items)
;; Avoid manual recursion for simple casesUse named let for complex loops:
(let loop ((items items) (acc '()))
(cond
((null? items) (reverse acc))
((valid? (car items))
(loop (cdr items) (cons (transform (car items)) acc)))
(else (loop (cdr items) acc))))Dicts
Prefer dicts over alists for data:
(define config
#{ host: "localhost"
port: 8080
debug: #f })
(dict-ref config port:) ; => 8080
(dict-set config debug: #t) ; => new dict
(dict-merge defaults user-config) ; => merged dictUse dict-get-in for nested access:
(dict-get-in response '(body: data: items:))Keyword Arguments
Use keywords for optional parameters:
(define (connect host (keys: (port 80) (timeout 30)))
...)
(connect "localhost")
(connect "localhost" port: 443 timeout: 60)Error Handling
Use guard for expected errors:
(guard (e ((file-error? e) #f))
(read-file-string path))Use error for unexpected conditions:
(unless user
(error "User not found: ~a" id))Boolean Expressions
Use and/or for short-circuit logic:
(or (find-cached key) (compute-value key))
(and (valid? x) (authorized? user) (process x))Avoid redundant boolean conversion:
;; Good
(not (null? items))
;; Bad
(if (not (null? items)) #t #f)Multi-List Operations
For iterating multiple lists together, use (sigil list):
(import (sigil list))
(list-map + '(1 2 3) '(10 20 30)) ; => (11 22 33)
(list-fold (lambda (a b acc) (cons (+ a b) acc))
'() '(1 2) '(10 20)) ; => (22 11)Module Organization
(define-library (myapp feature)
(import (sigil string))
(export public-api)
(begin
;; Private helpers first
(define (helper x) ...)
;; Public API
(define (public-api x)
(helper x))))