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-Clause
dev-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)))))