Commitc68556fcRecorded31 Mar 2026Repositorysigil-fp
Extract functional programming utilities from sigil-stdlib
Message
Standalone package with function composition (compose, pipe), partial application (partial, partial-right), utilities (const, flip, complement, juxt), and threading macros (chain, ->, some->).
Changed
.gitignore | 1 +
README.md | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dev-redirects.sgl | 6 ++++++
package.sgl | 23 +++++++++++++++++++++
src/sigil/fp.sgl | 44 +++++++++++++++++++++++++++++++++++++++
src/sigil/fp/chain.sgl | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/sigil/fp/fn.sgl | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 548 insertions(+)Diff
.gitignoreadded
@@ -0,0 +1 @@
+1
build/README.mdadded
@@ -0,0 +1,141 @@
+1
# sigil-fp+2
+3
Functional programming utilities for [Sigil](https://codeberg.org/sigil/sigil), providing function composition, partial application, and threading macros.+4
+5
## Installation+6
+7
Add to your `package.sgl` dependencies:+8
+9
```scheme+10
(from-git url: "codeberg:sigil/sigil-fp")+11
```+12
+13
## Modules+14
+15
### (sigil fp fn) - Function Composition+16
+17
Utilities for composing and partially applying functions.+18
+19
```scheme+20
(import (sigil fp fn))+21
```+22
+23
#### partial / partial-right+24
+25
Create new functions by fixing arguments:+26
+27
```scheme+28
(define add10 (partial + 10))+29
(add10 5) ; => 15+30
(add10 1 2 3) ; => 16+31
+32
(define div-by-2 (partial-right / 2))+33
(div-by-2 10) ; => 5+34
```+35
+36
#### compose+37
+38
Right-to-left function composition:+39
+40
```scheme+41
(define process (compose string-upcase string-trim))+42
(process " hello ") ; => "HELLO"+43
+44
((compose) 42) ; => 42 (identity)+45
```+46
+47
#### pipe+48
+49
Left-to-right function composition (pipeline style):+50
+51
```scheme+52
(define process (pipe string-trim string-upcase))+53
(process " hello ") ; => "HELLO"+54
```+55
+56
#### const+57
+58
Return a function that always returns the given value:+59
+60
```scheme+61
(map (const 0) '(a b c)) ; => (0 0 0)+62
```+63
+64
#### flip+65
+66
Swap the first two arguments of a function:+67
+68
```scheme+69
((flip cons) '(1 2) 'a) ; => (a 1 2)+70
((flip -) 3 10) ; => 7+71
```+72
+73
#### complement+74
+75
Return the logical complement of a predicate:+76
+77
```scheme+78
(filter (complement zero?) '(0 1 0 2 0 3)) ; => (1 2 3)+79
```+80
+81
#### juxt+82
+83
Apply multiple functions to the same arguments, returning a list of results:+84
+85
```scheme+86
((juxt car cdr) '(1 2 3)) ; => (1 (2 3))+87
((juxt + - *) 3 4) ; => (7 -1 12)+88
```+89
+90
### (sigil fp chain) - Threading Macros+91
+92
Thread values through sequences of expressions, similar to Clojure's threading macros and SRFI-197.+93
+94
```scheme+95
(import (sigil fp chain))+96
```+97
+98
#### chain / ->+99
+100
Thread a value through expressions. Uses `_` as a placeholder for the threaded value. When no `_` is present, the value is inserted as the first argument.+101
+102
```scheme+103
;; Thread-first (no placeholder)+104
(chain 5 (+ 3) (* 2))+105
; => 16+106
+107
;; Explicit placeholder+108
(chain '(1 2 3)+109
(map (lambda (x) (* x 2)) _)+110
(apply + _))+111
; => 12+112
+113
;; Placeholder in various positions+114
(chain 10 (- 20 _)) ; => 10+115
(chain 5 (list 1 2 _ 4)) ; => (1 2 5 4)+116
+117
;; -> is an alias for chain+118
(-> 5 (+ 3) (* 2)) ; => 16+119
```+120
+121
#### some->+122
+123
Like `->`, but short-circuits and returns `#f` if any step produces `#f`:+124
+125
```scheme+126
;; Short-circuits on #f+127
(some-> #f (+ 1 _)) ; => #f+128
+129
;; Continues while truthy+130
(some-> 5 (+ 1 _) (* 2 _)) ; => 12+131
+132
;; Useful for nullable pipelines+133
(some-> user+134
(dict-ref _ name:)+135
(string-split " " _)+136
car)+137
```+138
+139
## License+140
+141
BSD-3-Clausedev-redirects.sgladded
@@ -0,0 +1,6 @@
+1
;; Development redirects — point dependencies at local checkouts+2
(redirects+3
repos: (list+4
(for-repo+5
url: "codeberg:sigil/sigil"+6
use: (from-path dir: "../sigil"))))package.sgladded
@@ -0,0 +1,23 @@
+1
(define sigil-repo "codeberg:sigil/sigil")+2
+3
(package+4
name: "sigil-fp"+5
version: "0.9.0"+6
description: "Functional programming utilities for Sigil"+7
url: "https://codeberg.org/sigil/sigil-fp"+8
license: "BSD-3-Clause"+9
authors: (list "David Wilson <[email protected]>")+10
+11
configs: (list+12
(config name: 'dev output-dir: "build/dev" debug?: #t optimize: 0)+13
(config name: 'release output-dir: "build/release" debug?: #f optimize: 2))+14
+15
dependencies: (list+16
(from-git url: sigil-repo package: "sigil-stdlib"))+17
+18
tasks: (list+19
(task+20
name: 'build+21
description: "Compile sigil-fp modules"+22
steps: (list+23
(compile-sigil-modules sources: "src/**/*.sgl")))))src/sigil/fp.sgladded
@@ -0,0 +1,44 @@
+1
;;; (sigil fp) - Functional Programming Utilities for Sigil+2
;;;+3
;;; This package provides function composition, partial application,+4
;;; and threading macros for Sigil programs.+5
;;;+6
;;; Due to Sigil's module system not supporting re-exports, import+7
;;; the specific sub-modules you need:+8
;;;+9
;;; ```scheme+10
;;; ;; Function composition and partial application+11
;;; (import (sigil fp fn))+12
;;;+13
;;; ;; Threading macros (chain, ->, some->)+14
;;; (import (sigil fp chain))+15
;;;+16
;;; ;; Or import both+17
;;; (import (sigil fp fn)+18
;;; (sigil fp chain))+19
;;; ```+20
;;;+21
;;; ## Sub-modules+22
;;;+23
;;; - **(sigil fp fn)** - compose, pipe, partial, partial-right,+24
;;; const, flip, complement, juxt+25
;;; - **(sigil fp chain)** - chain, ->, some->+26
+27
(define-library (sigil fp)+28
(import (sigil fp fn)+29
(sigil fp chain))+30
(export+31
;; From (sigil fp fn)+32
partial+33
partial-right+34
compose+35
pipe+36
const+37
flip+38
complement+39
juxt+40
+41
;; From (sigil fp chain)+42
chain+43
->+44
some->))src/sigil/fp/chain.sgladded
@@ -0,0 +1,140 @@
+1
;;; (sigil fp chain) - Threading Macros+2
;;;+3
;;; Thread values through sequences of expressions, similar to+4
;;; Clojure's threading macros and SRFI-197.+5
;;;+6
;;; ## Basic Threading+7
;;;+8
;;; ```scheme+9
;;; (import (sigil fp chain))+10
;;;+11
;;; ;; Thread-first (no placeholder needed)+12
;;; (chain 5 (+ 3) (* 2))+13
;;; ; => 16 (same as (* (+ 5 3) 2))+14
;;;+15
;;; ;; Explicit placeholder with _+16
;;; (chain '(1 2 3)+17
;;; (map (lambda (x) (* x 2)) _)+18
;;; (apply + _))+19
;;; ; => 12+20
;;; ```+21
;;;+22
;;; ## Short-Circuit Threading+23
;;;+24
;;; ```scheme+25
;;; ;; Returns #f if any step produces #f+26
;;; (some-> user+27
;;; (dict-ref _ name:)+28
;;; (string-split " " _)+29
;;; car)+30
;;; ```+31
+32
(define-library (sigil fp chain)+33
(export chain -> some->)+34
+35
(begin+36
+37
;;; Thread a value through a sequence of expressions.+38
;;;+39
;;; Similar to SRFI-197's `chain`. Uses `_` as the placeholder symbol.+40
;;; When a step contains `_`, the threaded value is substituted there.+41
;;; When no `_` is present, the value is inserted as the first argument.+42
;;;+43
;;; ```scheme+44
;;; ;; Thread-first (no placeholder)+45
;;; (chain 5 (+ 3) (* 2))+46
;;; ; => 16 (same as (* (+ 5 3) 2))+47
;;;+48
;;; ;; Explicit placeholder+49
;;; (chain '(1 2 3)+50
;;; (map (lambda (x) (* x 2)) _)+51
;;; (apply + _))+52
;;; ; => 12+53
;;;+54
;;; ;; Placeholder in various positions+55
;;; (chain 10 (- 20 _)) ; => 10 (= 20 - 10)+56
;;; (chain 5 (list 1 2 _ 4)) ; => (1 2 5 4)+57
;;; ```+58
(define-syntax chain+59
(syntax-rules (_ %step)+60
;; Base case: just the value+61
((chain expr)+62
expr)+63
+64
;; Internal: process step result and continue+65
((chain %step result)+66
result)+67
((chain %step result next-step rest ...)+68
(chain result next-step rest ...))+69
+70
;; Step patterns: _ as entire step (identity)+71
((chain expr _ rest ...)+72
(chain %step expr rest ...))+73
+74
;; Step patterns: _ in first position (function position)+75
((chain expr (_ arg ...) rest ...)+76
(chain %step (expr arg ...) rest ...))+77
+78
;; Step patterns: _ in second position (first arg)+79
((chain expr (f _ arg ...) rest ...)+80
(chain %step (f expr arg ...) rest ...))+81
+82
;; Step patterns: _ in third position (second arg)+83
((chain expr (f a _ arg ...) rest ...)+84
(chain %step (f a expr arg ...) rest ...))+85
+86
;; Step patterns: _ in fourth position (third arg)+87
((chain expr (f a b _ arg ...) rest ...)+88
(chain %step (f a b expr arg ...) rest ...))+89
+90
;; Step patterns: _ in fifth position (fourth arg)+91
((chain expr (f a b c _ arg ...) rest ...)+92
(chain %step (f a b c expr arg ...) rest ...))+93
+94
;; Default for lists: no _ found, insert as first arg (thread-first)+95
((chain expr (f arg ...) rest ...)+96
(chain %step (f expr arg ...) rest ...))+97
+98
;; Bare identifier - call with expr as arg (must come last)+99
((chain expr f rest ...)+100
(chain %step (f expr) rest ...))))+101
+102
;;; Alias for `chain` (Clojure-style threading operator).+103
;;;+104
;;; ```scheme+105
;;; (-> 5 (+ 3) (* 2)) ; => 16+106
;;; ```+107
(define-syntax ->+108
(syntax-rules ()+109
((-> . args)+110
(chain . args))))+111
+112
;;; Thread value through steps, short-circuiting on #f.+113
;;;+114
;;; Like `->`, but stops and returns `#f` if any step produces `#f`.+115
;;; Useful for optional/nullable value pipelines.+116
;;;+117
;;; ```scheme+118
;;; ;; Returns #f if any step fails+119
;;; (some-> user+120
;;; (dict-ref _ name:)+121
;;; (string-split " " _)+122
;;; car)+123
;;;+124
;;; ;; Short-circuits on #f+125
;;; (some-> #f (+ 1 _)) ; => #f (doesn't call +)+126
;;;+127
;;; ;; Continues while truthy+128
;;; (some-> 5 (+ 1 _) (* 2 _)) ; => 12+129
;;; ```+130
(define-syntax some->+131
(syntax-rules ()+132
;; Base case: just the value+133
((some-> expr)+134
expr)+135
;; One or more steps: evaluate expr, check for #f, then continue+136
((some-> expr step rest ...)+137
(let ((%v expr))+138
(if %v+139
(some-> (-> %v step) rest ...)+140
#f)))))))src/sigil/fp/fn.sgladded
@@ -0,0 +1,193 @@
+1
;;; (sigil fp fn) - Function Composition Utilities+2
;;;+3
;;; Provides utilities for composing and partially applying functions,+4
;;; enabling point-free programming style.+5
;;;+6
;;; ## Partial Application+7
;;;+8
;;; Create new functions by fixing some arguments:+9
;;;+10
;;; ```scheme+11
;;; (import (sigil fp fn))+12
;;;+13
;;; ;; Fix the first argument+14
;;; (define add1 (partial + 1))+15
;;; (add1 5) ; => 6+16
;;;+17
;;; ;; Fix multiple arguments+18
;;; (define greet (partial string-append "Hello, "))+19
;;; (greet "World!") ; => "Hello, World!"+20
;;; ```+21
;;;+22
;;; ## Function Composition+23
;;;+24
;;; Combine functions into pipelines:+25
;;;+26
;;; ```scheme+27
;;; ;; Right-to-left composition (mathematical style)+28
;;; (define process (compose string-upcase string-trim))+29
;;; (process " hello ") ; => "HELLO"+30
;;;+31
;;; ;; Left-to-right composition (pipeline style)+32
;;; (define process (pipe string-trim string-upcase))+33
;;; (process " hello ") ; => "HELLO"+34
;;; ```+35
;;;+36
;;; ## Constant and Flip+37
;;;+38
;;; ```scheme+39
;;; ;; Always return the same value+40
;;; (map (const 0) '(a b c)) ; => (0 0 0)+41
;;;+42
;;; ;; Swap argument order+43
;;; ((flip cons) '(1 2) 'a) ; => (a 1 2)+44
;;; ```+45
+46
(define-library (sigil fp fn)+47
(export+48
;; Partial application+49
partial+50
partial-right+51
+52
;; Function composition+53
compose+54
pipe+55
+56
;; Utilities+57
const+58
flip+59
complement+60
juxt)+61
+62
(begin+63
+64
;;; Create a new function with some arguments pre-filled from the left.+65
;;;+66
;;; The returned function accepts additional arguments which are appended+67
;;; to the fixed arguments when calling the original procedure.+68
;;;+69
;;; ```scheme+70
;;; (define add10 (partial + 10))+71
;;; (add10 5) ; => 15+72
;;; (add10 1 2 3) ; => 16+73
;;;+74
;;; (define greet (partial string-append "Hello, "))+75
;;; (greet "World!") ; => "Hello, World!"+76
;;; ```+77
(define (partial proc . fixed-args)+78
(: procedure? any? ... -> procedure?)+79
(lambda args+80
(apply proc (append fixed-args args))))+81
+82
;;; Create a new function with some arguments pre-filled from the right.+83
;;;+84
;;; The returned function accepts additional arguments which are prepended+85
;;; to the fixed arguments when calling the original procedure.+86
;;;+87
;;; ```scheme+88
;;; (define subtract-from-10 (partial-right - 10))+89
;;; (subtract-from-10 3) ; => -7 (= 3 - 10)+90
;;;+91
;;; (define div-by-2 (partial-right / 2))+92
;;; (div-by-2 10) ; => 5 (= 10 / 2)+93
;;; ```+94
(define (partial-right proc . fixed-args)+95
(: procedure? any? ... -> procedure?)+96
(lambda args+97
(apply proc (append args fixed-args))))+98
+99
;;; Compose functions right-to-left.+100
;;;+101
;;; Returns a function that applies the rightmost function first, then+102
;;; passes its result to the next function, and so on. With no arguments,+103
;;; returns `identity`.+104
;;;+105
;;; ```scheme+106
;;; ((compose f g h) x) ; => (f (g (h x)))+107
;;;+108
;;; (define process (compose string-upcase string-trim))+109
;;; (process " hello ") ; => "HELLO"+110
;;;+111
;;; ((compose) 42) ; => 42 (identity)+112
;;; ```+113
(define (compose . procs)+114
(: procedure? ... -> procedure?)+115
(cond+116
((null? procs) identity)+117
((null? (cdr procs)) (car procs))+118
(else+119
(let ((f (car procs))+120
(g (apply compose (cdr procs))))+121
(lambda (x) (f (g x)))))))+122
+123
;;; Compose functions left-to-right (pipeline style).+124
;;;+125
;;; Returns a function that applies the leftmost function first, then+126
;;; passes its result to the next function, and so on. This is the+127
;;; opposite of `compose` and often more intuitive for data pipelines.+128
;;;+129
;;; ```scheme+130
;;; ((pipe f g h) x) ; => (h (g (f x)))+131
;;;+132
;;; (define process (pipe string-trim string-upcase))+133
;;; (process " hello ") ; => "HELLO"+134
;;; ```+135
(define (pipe . procs)+136
(: procedure? ... -> procedure?)+137
(apply compose (reverse procs)))+138
+139
;;; Return a function that always returns the given value.+140
;;;+141
;;; Useful for providing constant values to higher-order functions.+142
;;;+143
;;; ```scheme+144
;;; (map (const 0) '(a b c)) ; => (0 0 0)+145
;;;+146
;;; (define always-true (const #t))+147
;;; (always-true 'anything) ; => #t+148
;;; ```+149
(define (const value)+150
(: any? -> procedure?)+151
(lambda args value))+152
+153
;;; Return a function with its first two arguments swapped.+154
;;;+155
;;; ```scheme+156
;;; ((flip cons) '(1 2) 'a) ; => (a 1 2)+157
;;; ((flip -) 3 10) ; => 7 (= 10 - 3)+158
;;; ((flip /) 2 10) ; => 5 (= 10 / 2)+159
;;; ```+160
(define (flip proc)+161
(: procedure? -> procedure?)+162
(lambda (a b . rest)+163
(apply proc b a rest)))+164
+165
;;; Return the logical complement of a predicate.+166
;;;+167
;;; ```scheme+168
;;; (define not-empty? (complement null?))+169
;;; (not-empty? '(1 2 3)) ; => #t+170
;;; (not-empty? '()) ; => #f+171
;;;+172
;;; (filter (complement zero?) '(0 1 0 2 0 3)) ; => (1 2 3)+173
;;; ```+174
(define (complement pred)+175
(: procedure? -> procedure?)+176
(lambda args+177
(not (apply pred args))))+178
+179
;;; Return a function that applies multiple functions to the same arguments.+180
;;;+181
;;; Returns a list of results, one from each function.+182
;;;+183
;;; ```scheme+184
;;; ((juxt car cdr) '(1 2 3)) ; => (1 (2 3))+185
;;; ((juxt + - *) 3 4) ; => (7 -1 12)+186
;;;+187
;;; (map (juxt string-upcase string-length) '("hi" "hello"))+188
;;; ; => (("HI" 2) ("HELLO" 5))+189
;;; ```+190
(define (juxt . procs)+191
(: procedure? ... -> procedure?)+192
(lambda args+193
(map (lambda (p) (apply p args)) procs)))))