Naming and Logic
programming essentials with Scheme
This book teaches the core of programming with Guile Scheme. It starts from scratch and teaches in examples with minimal explanation. Its core is a map of the minimal functionality you need from Scheme to write meaningful programs. It is translated from the article teaching Programming with Wisp.
📚 If you want to support me, please buy the pocket-format print-version from epubli or from your local book shop via ISBN-10: 3-565-02813-0 or ISBN-13: 978-3-565-02813-9. Reading on paper in transit is one of the best ways to learn.
I hope that one day this web version will be interactive, using Guile on wasm (which is currently being built by the folks at Spritely).
To follow along, install Guile and try the examples as you read.
This is Free Culture (cc by-sa and LGPLv3+).
The source is programming-scheme.org
Preface
Why this book? Providing a concise start, a no-frills, opinionated
intro to programming from first define to deploying an application
on just 64 pages.
Who is it for? You are a newcomer and want to learn by trying code examples? You know programming and want a running start into Scheme? You want to see how little suffices with Scheme’s practical minimalism? Then this book is for you.
What is Scheme? Scheme is a programming language — a Lisp — that follows the principle “design not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary”.
This book uses Guile Scheme, the official extension language of the
GNU project but is no official product of the FSF or GNU, nor endorsed
by them. GNU is a trademark of the Free Software Foundation. You can
find Guile on gnu.org/s/guile and the FSF on fsf.org.
How to get Guile? Download and install Guile from the website
www.gnu.org/software/guile — then open the REPL by executing
guile in the terminal. The REPL is where you type and try code
interactively. Some name it guile3.0. This text assumes GNU Linux.
The Map of Scheme
(How to make such a map? See Org Mode Tipp: imagemap)
Name a value with define
Use (define ...) to name a value. Return the last name.
(define small-tree-height-meters 3) (define large-tree-height-meters 5) small-tree-height-meters
After typing this into the Guile REPL, hit enter. You should see
$1 = 3
This means: the first returned value ($1) is 3. The next time you
return a value, it will be called $2.
Names can contain any letter except for (white-)space, quote, comma or parentheses. They must not be numbers.
(define illegal name 1) (define 'illegal-name 2) (define ,illegal-name 3) (define illegal)name 4) (define 1113841 5)
Add comments with ;
(define birch-height/m 3) ;; this is a comment (define height ; comment at the end ;; comment between lines 5)
It is common to use ;; instead of ;, but not required.
A comment goes from the first ; to the end of the line.
Compare numbers
(= 3 3)
#t
(= 3 5)
#f
#t means true, #f means false. Parentheses return the result of logic.
The logic comes first. This is clear for =, but easy to misread for <.
(< 3 5) ;; is 3 smaller than 5? #true
#t
(> 5 3) ;; is 5 bigger than 3? #true
#t
(> 3 3) ;; is 3 bigger than 3? #false
#f
(>= 3 3) ;; is 3 bigger than or equal to 3? #true
#t
(<= 3 3) ;; is 3 smaller than or equal to 3? #true
#t
Use infix in logic
Prefix comparisons can be hard to grasp when used to math. Guile
provides the keyword #!curly-infix to enable infix math between
curly braces: {...}.
#!curly-infix
{3 < 5}
#t
Infix logic directly gives a value. To use it, you must put
#!curly-infix somewhere in your code before you use the first curly
braces ({...}).
You can only use curly braces for infix operations.
Because infix-logic gives a value, you can use it in place of a value, for example to nest it:
#!curly-infix
{{5 < 3} equal? #f}
#t
Or to name its value:
#!curly-infix (define is-math-sane? {3 < 5}) is-math-sane?
#t
By convention, names with the value true or false have the suffix ?.
Use logic with true and false
(and #t #t)
#t
(and #f #t)
#f
(or #f #t)
#t
(or #f #f)
#f
If any value passed to and is #f (#false), it ignores further values.
If any value passed to or is not #f (not #false), it ignores further values.
#!curly-infix (and #t #t #t) ;; => #true (and #t #f #t) ;; => #false (and {3 < 5} {5 < 3}) ;; => #false (or #t #f #t) ;; => true (or {3 < 5} {5 < 3}) ;; => #true (or #f #f #f) ;; => #false
For and and or, everything is #true (#t) except for #false (#f).
Given the number of hard to trace errors in other languages that turn
up in production, this is the only sane policy.
Use named values in logic
#!curly-infix (define small-tree-height/m 3) (define large-tree-height/m 5) {small-tree-height/m < large-tree-height/m}
#t
Name the result of logic with parentheses
#!curly-infix (define birch-h/m 3) (define chestnut-h/m 5) (define same-héight? (= birch-h/m chestnut-h/m)) (define smaller? {birch-h/m < chestnut-h/m}) ;; infix smaller?
#t
The infix directly returns its value. Here this value is then named smaller?.
Name logic with define (
(define (same-height? tree-height-a tree-height-b) (= tree-height-a tree-height-b)) (same-height? 3 3)
#t
The first word is the name of the logic. The others are the arguments the logic operates on.
By convention, logic that returns true or false has the suffix ?.
You can now use your named logic like all other logic. Even with infix.
#!curly-infix (define (same-height? tree-height-a tree-height-b) (= tree-height-a tree-height-b)) {3 same-height? 3}
#t
What this map of Scheme calls named logic is commonly called
function or procedure. We’ll stick with logic for the sake of
a leaner conceptual mapping.
The indented lines with the logic named here are called the body. The body of named logic can have multiple lines. Only the value of the last is returned.
(define (unused-comp value) (= 2 value) ;; not returned (= 3 value)) ;; returned
Name a name with define
(define small-tree-height-meters 3) (define height small-tree-height-meters) height
3
After you name a name, the new name holds the same value as the original name.
Return the value of logic
#!curly-infix (define (larger-than-4? size) {size > 4}) larger-than-4?
#<procedure larger-than-4? (size)>
The value of logic defined with define ( is a procedure — often
called proc for brevity. You can see the arguments in the output: If
you call it with too few or too many arguments, you get errors.
There are other kinds of logic: syntax rules and reader-macros. We
will cover syntax rules later. New reader macros are rarely needed;
using {...} for infix math is a reader macro activated with
#!curly-infix.
Name in define ( with define
#!curly-infix (define birch-h/m 3) (define (birch-is-small) (define reference-h/m 4) {birch-h/m < reference-h/m}) (birch-is-small)
#t
Only the last part of the body of define ( is returned.
A calculation inside parentheses or curly braces is executed in-place, so when it is the last element, its result value is returned.
A Zen for Scheme part 1: Birds Eye
- RR
- Remove limitations to Reduce the feature-count you need,
but OM: Optimizability Matters. - FI
- Freedom for Implementations and from Implementations,
but CM: Community Matters: Join the one you choose. - SL
- Mind the Small systems!
And the Large systems! - ES
- Errors should never pass silently,
unless speed is set higher than safety.
Thanks for the error-handling principle goes to John Cowan.
Return a list of values with list
(define known-heights (list 3 3.75 5 100)) (list (list 3 5) known-heights)
((3 5) (3 3.75 5 100))
You can put values on their own lines. Different from define (, list
keeps all values, not just the last.
(define known-heights-2 (list 3 3.75 5 100)) ;; list keeps all numbers (define known-heights-3 (list 3 3.75 5 100)) (define (last-height) 3 3.75 5 100) ;; (define (...) ...) only returns the last (= 100 (last-height))
#t
Name the result of logic in one line with ()
(define birch-h/m 3) (define chestnut-h/m 5) (define same-height? (= birch-h/m chestnut-h/m)) same-height?
#f
This is consistent with infix-math and uniform with defining logic:
#!curly-infix (define birch-h/m 3) (define chestnut-h/m 5) (define same-height? {birch-h/m = chestnut-h/m}) (define (same? tree-height-a tree-height-b) (= tree-height-a tree-height-b)) (define same2? (same? birch-h/m chestnut-h/m)) (list same-height? same2?)
'(#f #f)
Name text with "
(define tree-description "large tree") (define footer "In Love, Arne") (define greeting "Hello") (display footer)
In Love, Arne
Like { }, text (called string as in “string of characters”) is its
value.
Text can span multiple lines. Linebreaks in text do not affect the meaning of code.
You can use \n to add a line break within text without having a
visual line break. The backslash (\) is the escape character and
\n represents a line break. To type a real \ within quotes ( "
), you must escape it as \\.
With display you can show text as it will look in an editor.
Text is stronger than comments, unless it is inside a comment:
(define with-comment ;; belongs to coment ;; comment "quotes inside comment" "Hello ;; part of the text. Backslash: \\") with-comment
Hello ;; part of the text. Backslash: \
Take decisions with cond
#!curly-infix (define chestnut-h/m 5) (define tree-description (cond ({chestnut-h/m > 4} "large tree") ((= 4 chestnut-h/m) "four meter tree") (else "small tree"))) tree-description
large tree
cond checks its clauses one by one and uses the first with value
#true.
To cond, every valid value is #true (#t) except for #false
(#f).
To use named logic, enclose it in parentheses to check its value.
In cond, else is #true, too.
#!curly-infix (list (cond (5 #t) (else ;; else is #true in cond #f)) (cond (#f #f) (else #t)) (cond ({3 < 5} #t) (else #f)))
'(#t #t #t)
Use fine-grained numbers with number-literals
(define more-precise-height 5.32517) (define 100-meters 1e2) (list more-precise-height 100-meters)
(5.32517 100.0)
These are floating point numbers. They store approximate values in 64 bit binary, depending on the platform. Read all the details in the Guile Reference manual Real and Rational Numbers, the r5rs numbers, and IEEE 754.
Use exact numbers with #e and quotients
(define exactly-1/5 #e0.2) (define exactly-1/5-too 1/5) (list exactly-1/5 exactly-1/5-too)
(1/5 1/5)
Guile computations with exact numbers stay reasonably fast even for unreasonably large or small numbers.
Turn exact numbers into decimals with exact->inexact
(list (exact->inexact #e0.2)
(exact->inexact 1/5)
(exact->inexact 2e7))
(0.2 0.2 2.0e7)
The inverse is inexact->exact:
(inexact->exact 0.5)
1/2
Note that a regular 0.2 need not be exactly 1/5, because floating
point numbers do not have exact representation for that. You’ll need
#e to have precise 0.2.
(list (inexact->exact 0.2)
#e0.2)
(3602879701896397/18014398509481984 1/5)
By convention, procs that convert data type a into data type b are named a->b.
Use math with the usual operators as logic
(define one-hundred (* 10 10)) (define half-hundred (/ one-hundred 2)) half-hundred
50
Remember that names cannot be valid numbers!
(define 100 ;; error! (* 10 10))
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Syntax error:
unknown location: source expression failed to match any pattern in form (define 100 (* 10 10))
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
Using infix via curly braces {} is useful for math:
#!curly-infix (define one-hundred {10 * 10}) (define half-hundred {one-hundred / 2}) half-hundred
50
Compare structural values with equal?
(define known-heights (list 3 3.75 5 100)) (define known-heights-2 (list 3 3.75 5 100)) (define known-heights-3 (list 3 3.75 5 100)) (equal? known-heights known-heights-2 known-heights-3)
#t
equal? compares whether all values it receives are equal. For
structural values like lists, being equal means that all contained
elements have equal value and are in the same order.
Like = and +, equal? can be used on arbitrary numbers of values.
A Zen for Scheme part 2: On the Ground
- HA
- Hygiene reduces Anxiety,
except where it blocks your path. - PP
- Practicality beats Purity,
except where it leads into a dead end. - 3P
- 3 Pillars of improvement:
Experimentation, Implementation, Standardization.
Apply logic to a list of values with apply
(apply = (list 3 3))
#t
(equal? (= 3 3) (apply = (list 3 3)))
#t
Only the last argument of apply is treated as list, so you can give initial arguments:
(define a 1) (define b 1) (apply = a b (list 1 1)) ;; becomes (= a b 1 1)
#t
(define zero-to-nine (iota 10)) ;; numbers from 0 to 9 (apply + zero-to-nine) ;; sums the numbers
45
Using apply proc a (list b c) gives the same result as calling proc
with the arguments a b c. In the examples, proc is = or +.
Get the arguments of named logic as list with . rest
(define (same? heights) ;; accepts a list (apply = heights)) (define (same2? . heights) ;; accepts multiple arguments (apply = heights)) (list (same? (list 1 1 1)) (same2? 1 1 1))
'(#t #t)
These are often called rest. Getting them is not for efficiency but
to improve programming interfaces (APIs). The list creation happens
implicitly. You can mix regular arguments and rest arguments:
(define (same? alice bob . rest) (display (list alice bob rest)) (newline) (apply = alice bob rest)) (display (same? 1 1 1 1)) ;; 4 arguments, the last 2 are rest
(1 1 (1 1)) #t
Remember that apply uses only the last of its arguments as list, in symmetry with . rest.
Change the value or logic of a defined name with set!
(define birch-h/m 3) (set! birch-h/m 3.74) ;; set to a value (display birch-h/m)(newline) ;; (newline) is linebreak (set! birch-h/m =) ;; set to logic (display birch-h/m)(newline)
3.74 #<procedure = (#:optional _ _ . _)>
It is customary to suffix named logic that changes values of existing names with !.
Since logic can cause changes to names and not just return a result,
it is not called function, but procedure; proc for brevity.
Apply logic to each value in lists and ignoring the results with for-each
#!curly-infix (define birch-h/m 3) (define has-birch-height #f) (define (set-true-if-birch-height! height/m) (cond ({birch-h/m = height/m} (set! has-birch-height #t)))) (define heights (list 3 3.75 5 100)) (for-each set-true-if-birch-height! heights) has-birch-height
#t
Get the result of applying logic to each value in lists with map
(define birch-h/m 3) (define (same-height-as-birch? height/m) (= birch-h/m height/m)) (define heights (list 3 3.75 5 100)) (list heights (map same-height-as-birch? heights) (map + ;; becomes 1+3 2+2 3+1 (list 1 2 3) (list 3 2 1)) (map list (list 1 2 3) (list 3 2 1)))
'((3 3.75 5 100) (#t #f #f #f) (4 4 4) ((1 3) (2 2) (3 1)))
When operating on multiple lists, map takes one argument from each
list. All lists must be the same length.
To remember: apply extracts the values from its last argument,
map extracts one value from each argument after the first.
apply map list ... flips rows and colums:
(apply map list
(list (list 1 2 3)
(list 3 2 1)))
((1 3) (2 2) (3 1))
Reuse your logic with let-recursion
Remember the for-each example:
#!curly-infix (define has-birch-height #f) (define heights (list 3 3.75 5 100)) (define (set-true-if-birch-height! height/m) (define birch-h/m 3) (cond ({birch-h/m = height/m} (set! has-birch-height #t)))) (for-each set-true-if-birch-height! heights) has-birch-height
#t
Instead of for-each, we can build our own iteration:
(define heights (list 3 3.75 5 100)) (define (has-birch-height? heights) (define birch-h/m 3) (let loop ((heights heights)) ;; start with heights (cond ((null? heights) #f) ;; if heights is empty: #false ((= birch-h/m (car heights)) ;; car gets the first #t) (else ;; continue with all but the first (loop (cdr heights)))))) (has-birch-height? heights)
#t
null? asks whether the list is empty. car gets the first element
of a list, cdr gets the list without its first element.
Recursion is usually easier to debug (all variable elements are available at the top of the let recursion) and often creates cleaner APIs than iteration, because fewer names are visible from outside.
As rule of thumb: start with the recursion end condition (here:
(null? heights)) and ensure that each branch of the cond either
ends recursion by returning something (here #f or #t) or moves a
step towards finishing (usually with cdr, +, -, 1+, or 1-).
Another example why recursion wins:
(define (fib n) (let rek ((i 0) (u 1) (v 1)) (if (>= i (- n 2)) v (rek (+ i 1) v (+ u v)))))
A Zen for Scheme part 3: Submerged in Code
- WM
- Use the Weakest Method that gets the job done,
but know the stronger methods to employ them as needed. - RW
- Recursion Wins,
except where a loop-macro is clearer. - RM
- Readability matters,
and nesting works.
Import pre-defined named logic and values with import
(import (ice-9 pretty-print) (srfi :1 lists)) ;; list operations like (first) (pretty-print (list 12 (list 34) 5 6)) (pretty-print (list (first (list 1 2 3)) ;; 1 (second (list 1 2 3)) ;; 2 (third (list 1 2 3)) ;; 3 (member 2 (list 1 2 3)))) ;; list 2 3 => #true
(12 (34) 5 6) (1 2 3 (2 3))
Import uses modules which can have multiple components. In the first
import, ice-9 is one component and the second is pretty-print. In
the second, srfi is the first component, :1 is the second, and
lists is the third.
ice-9 is the name for the core extensions of Guile. It’s a play of words on ice-nine, a fictional perfect seed crystal.
SRFI’s are Scheme Requests For Implementation, portable libraries
built in collaboration between different Scheme implementations. The
ones available in Guile can be found in the Guile Reference manual.
More can be found on srfi.schemers.org. They are imported by number
(:1) and can have a third component with a name, but that’s not
required.
You can use only to import only specific names.
(import (only (srfi :1) first second)) ;; no third (first (list 1 2 3)) ;; 1 (second (list 1 2 3)) ;; 2 (third (list 1 2 3)) ;; error: third not imported
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Unbound variable: third
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
Optimize for performance with ,profile
When in the interactive REPL, you can get a runtime profile; for fast operations you may need to run them in a loop to get a robust sample:
(define runs (iota 1000000)) ;; numbers from 0 to 999999 ,profile (for-each number->string runs)
% cumulative self time seconds seconds procedure 100.00 0.06 0.06 number->string 0.00 0.06 0.00 ice-9/boot-9.scm:260:2:for-each --- Sample count: 3 Total time: 0.057737025 seconds (0.0 seconds in GC)
To look deeper, use ,disassemble.
In code files import statprof.
Create nameless logic with lambda
(define (is-same-height? a b) (> a b)) ;; <- this is a mistake: differs from the name (display is-same-height?) (newline) ; (newline) prints a linebreak (display (is-same-height? 3 3))(newline) ;; wrong result (define (fixed a b) (= a b)) (set! is-same-height? fixed) (display is-same-height?)(newline) ;; now called "fixed" (display (is-same-height? 3 3))(newline) ;; shorter and avoiding name pollution and confusion. (set! is-same-height? (lambda (a b) ;; no name, only arguments (= a b))) ;; since lambda has no name, we see the original name (display is-same-height?)(newline) (display (is-same-height? 3 3))
#<procedure is-same-height? (a b)> #f #<procedure fixed (a b)> #t #<procedure is-same-height? (a b)> #t
The return value of lambda is logic (a procedure).
If logic is defined via define (, it knows the name it has been
defined as. With lambda, it does not know the name.
lambda is a special form. Think of it as
(define (name arguments ...) ...), but without the name.
Extend a list with cons
The core of composing elementwise operations.
To build your own map function which returns a list of results, you
need to add to a list.
(cons 1 (list 2 3)) ;; => list 1 2 3
| 1 | 2 | 3 |
Used for a simplified map implementation that takes a single list:
(define (single-map proc elements) (let loop ((changed (list)) (elements elements)) (cond ((null? elements) ;; empty list => done (reverse changed)) ;; recover order (else (loop ;; add processed first element to changed (cons (proc (car elements)) ; car gets first changed) ;; drop first element from elements (cdr elements)))))) (single-map even? (list 1 2 3)) ;; => #f #t #f
'(#f #t #f)
Repeatedly cons’ing the first element of a list onto a second list and dropping it from the first reverses the element order. It’s a core operation, because it’s the most efficient operation for singly linked lists.
Mutate partially shared state with list-set!
The elements in a list are linked from its start. Different lists can share the same tail when you cons onto the same partial list.
(import (srfi :1) (ice-9 pretty-print)) (define tail ;; the shared tail (list 3 2 1)) ;; 3 2 1 (define four ;; an intermediate list (cons 4 tail)) ;; 4 3 2 1 (define five ;; one more list (cons 5 four)) ;; 5 4 3 2 1 (define fourtytwo ;; branching off from tail (cons 42 tail)) ;; 42 3 2 1 (list-set! five 1 "four") ;; change shared state (pretty-print five) ;; changed directly: 5 four 3 2 1 (pretty-print four) ;; touched indirectly ;; four 3 2 1 (pretty-print fourtytwo) ;; not affected ;; 42 3 2 1 (list-set! tail 1 "two") ;; mutating the shared tail (pretty-print five) ;; 5 four 3 two 1 (pretty-print four) ;; four 3 two 1 (pretty-print fourtytwo) ;; 42 3 two 1 (pretty-print tail) ;; 3 two 1
(5 "four" 3 2 1)
("four" 3 2 1)
(42 3 2 1)
(5 "four" 3 "two" 1)
("four" 3 "two" 1)
(42 3 "two" 1)
(3 "two" 1)
Mutating shared state often causes mistakes. Use it only when needed.
Apply partial procedures with srfi :26 cut
(import (srfi :26 cut)) (define (plus-3 number) (+ 3 number)) (define plus-3-cut (cut + 3 <>)) (list (map plus-3 (list 1 2 3)) ;; list 4 5 6 (map plus-3-cut (list 1 2 3)) ;; list 4 5 6 (map (cut + 3 <>) (list 1 2 3)) ;; list 4 5 6 (map (cut - <> 1) ;; => {<> - 1} (list 1 2 3)) ;; list 0 1 2 (map (cut - 1 <>) ;; => {1 - <>} (list 1 2 3))) ;; list 0 -1 -2
((4 5 6) (4 5 6) (4 5 6) (0 1 2) (0 -1 -2))
cut enables more concise definition of derived logic. These
specialized definitions also help to simplify and=>:
(list (and=> #f (cut + <> 3) ;; #f (and=> 5 (cut + <> 3)) ;; 8
This method is known in mathematics as “currying“.
Get and resolve names used in code with quote, eval, and module-ref
(list (quote alice)
'bob ;; shorthand for (quote bob)
'carol
(quote dave))
;; => (alice bob carol dave)
(define alice "the first")
(eval 'alice (current-module))
;; => "the first"
(module-ref (current-module) 'alice)
;; => "the first"
;; module-ref is less powerful than eval. And safer.
(define code
(quote
(list 1 2 3)))
code
;; => (list 1 2 3)
;; uses parentheses form
(eval code (current-module))
;; => (1 2 3)
'(1 2 3)
;; (1 2 3)
(list 1 2 3)
;; (1 2 3)
(equal? '(1 2 3)
(list 1 2 3)) ;; #true
The form '(1 2 3) is a shorthand to create an immutable
(literal) list that is equal? to list 1 2 3.
But some operations like (list-set! the-list index new-value) from srfi :1 do not work on immutable lists.
(define mutable-list (list 1 2 3)) (display mutable-list) (newline) (list-set! mutable-list 1 'a) ;; zero-indexed (display mutable-list)
(1 2 3) (1 a 3)
(define immutable-list '(1 2 3)) (display immutable-list) (list-set! immutable-list 1 'a) ;; error!
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure set-car!: Wrong type argument in position 1 (expecting mutable pair): (2 3)
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
Use r7rs datatypes, e.g. with vector-map
R7RS is the 7th Revised Report
on Scheme. Guile provides a superset of the standard: its core can be
imported as scheme base. A foundational datatype is Vectors with
O(1) random access guarantee.
(import (scheme base)) (define vec (list->vector (list 1 b "third"))) (vector-ref vec 0) ;; zero-indexed
To map over vectors, use vector-map:
(import (scheme base)) (define vec (list->vector (list 1 b "third"))) (vector-map (λ (element) (cons 'el element)) vec)
Vectors have the literal form #(a b c). It is an error to mutate these.
(import (scheme base)) (define mutable-vector (list->vector (list 1 b "third"))) (define literal-vector #(1 b "third")) (vector-set! mutable-vector 1 "bee") ;; allowed ; (vector-set! literal-vector 1 "bee") ;; forbidden (list mutable-vector literal-vector)
Name structured values with define-record-type
(import (srfi :9 records)) (define-record-type <tree> (make-tree type height-m weight-kg carbon-kg) tree? (type tree-type) (height-m tree-height) (weight-kg tree-weight) (carbon-kg tree-carbon)) (define birch-young (make-tree "birch" 13 90 45)) ;; 10 year, 10cm diameter, (define birch-old (make-tree "birch" 30 5301 2650)) ;; 50 year, 50cm (define birch-weights (map tree-weight (list birch-young birch-old))) (list birch-young birch-old birch-weights)
'(#<<tree> type: "birch" height-m: 13 weight-kg: 90 carbon-kg: 45> #<<tree> type: "birch" height-m: 30 weight-kg: 5301 carbon-kg: 2650> (90 5301))
Carbon content in birch trees is about 46% to 50.6% of the mass. See forestry commission technical paper 1993.
Height from Waldwissen, weight from BaumUndErde.
Create your own modules with define-module
To provide your own module, create a file named by the module name.
For (import (example trees)) the file must be example/trees.scm. Use
define-module and #:export what others can import:
(define-module (example-scm trees) #:export (birch-young make-tree tree? tree-carbon)) (import (srfi :9 records)) ;; imports after define-module (define-record-type <tree> ;; reduced record type (make-tree carbon-kg) tree? (carbon-kg tree-carbon)) (define birch-young (make-tree 45)) ;; 10 year, 10cm diameter,
To use that module, add your root folder to the search path. Then just import it. To ensure that the file is run that way, use shell-indirection:
#!/usr/bin/env bash exec -a "${0}" guile -L "$(dirname "${0}")" "${0}" $@ ;; Guile execution !# (import (example-scm trees)) birch-young
$1 = #<<tree> carbon-kg: 45>
Made executable with chmod +x the-file.scm, run with ./the-file.scm
Handle errors using with-exception-handler
;; unhandled exception stops execution (define (add-5 input) (display (+ 5 input))) ;; illegal for text ;; (map add-5 '("five" 6 "seven")) ;; Wrong type argument ;; check inputs (define (add-5-if input) (if (number? input) (display (+ 5 input)) #f)) (map add-5-if '("five" 6 "seven"))
11
;; handle exceptions (define (add-5-handler input) (with-exception-handler (λ (e) (format #t "must be number, is ~S.\n" input) #f) ;; this is the handler (λ () (display (+ 5 input))(newline)) ;; the code #:unwind? #t)) ;; #t: continue #f: stop (map add-5-handler '("five" 6 "seven"))
must be number, is "five". 11 must be number, is "seven".
In Guile Scheme checking inputs is often cheaper than exception handling, but exception handling needs less information about implementation details.
Format replaces patterns (here: ~s) in text with values (here
input).
Debug with backtraces
To find the cause of an exception, you can use backtraces. When you
see an error interactively, you can get a backtrace with ,bt:
(define (add-5 input) (display (+ 5 input))) ;; illegal for text (add-5 "six")
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure +: Wrong type argument in position 1: "six"
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
,bt
In current input:
2:11 1 (add-5 "six")
In ice-9/boot-9.scm:
1683:22 0 (raise-exception _ #:continuable? _)
The error comes from the first item (add-5 "six"), and it is thrown
in the file ice-9/boot-9.scm via raise-exception. When the error
comes from a file, you also get a file name and line number for the
first item. Most backtraces are longer.
The number in square brackets after the interactive error ([1]) is
your depth in the debugger. You can get one level back with ,q or
CTRL-D (when outside the debugger, this exits from Guile).
To throw debuggable errors yourself, use raise-exception.
Also see the chapter Exceptions in the Guile Reference Manual.
Test your code with srfi 64
Is your code correct?
(import (srfi :64 testsuite)) (define (tree-carbon weight-kg) (* 0.5 weight-kg)) (define (run-tests) (test-begin "tree-carbon") (test-equal 45.0 (tree-carbon 90)) (test-approximate 45.0 (+ 40 (random 10.0)) 5) ;; expected error size (test-assert (equal? 45.0 (tree-carbon 90))) (test-error (throw 'wrong-value)) (test-end "tree-carbon")) (run-tests)
%%%% Starting test tree-carbon (Writing full log to "tree-carbon.log") # of expected passes 4
You can use this anywhere.
For details, see srfi 64.
Define derived logic structures with define-syntax-rule
In procedures, arguments are evaluated to their return value before
the body runs. Procedures evaluate from inside to outside:
(import (ice-9 pretty-print)) (define (hello-printer . args) (pretty-print "Hello") (for-each pretty-print args)) ;; v evaluated first (hello-printer 1 (pretty-print "second") 3 4) ;; prints "second" "Hello" 1 3 4
"second" "Hello" 1 #<unspecified> 3 4
The result of pretty-print is #<unspecified>
(pretty-print "second") is evaluated before being passed to
hello-printer, so its result is shown first.
But for example cond only evaluates the required branches. It is not
a procedure, but a syntax-rule.
Syntax-rules evaluate from outside to inside:
(import (ice-9 pretty-print)) (define-syntax-rule (early-printer args ...) (begin (pretty-print "Hello") ;; <- evaluated first (for-each pretty-print (list args ...)))) (early-printer 1 (pretty-print "second") 3 4) ;; prints "Hello" "second" 1 3 4
"Hello" "second" 1 #<unspecified> 3 4
Arguments of define-syntax-rule are only evaluated when they are
passed into a regular procedure or returned. By calling other
syntax-rules in syntax-rules, evaluation can be delayed further.
define-syntax-rule can reorder arguments and pass them to other syntax-rules
and to procedures. It cannot ask for argument
values, because it does not evaluate names as values. It operates on
names and structure.
Instead of (define (name . args) args), it uses a pattern:
(define-syntax-rule (name args ...) args ...)
The ellipsis ... marks args as standing for zero or more names. It
must be used with the ellipsis as args ....
The body of define-syntax-rule must have only one element. The logic
begin wraps its own body to count as only one element. It returns
the value of the last element in its body.
Build value-lists with quasiquote and unquote
(define (tree-manual type height weight carbon-content) "Create a tree with list and cons." (list (cons 'type type) (cons 'height height ) (cons 'weight weight) (cons 'carbon-content carbon-content))) (tree-manual "birch" 13 90 45) (define (tree-quasiquote type height weight carbon-content) "Create a tree with raw quasiquote and unquote." (quasiquote ((type . (unquote type)) (height . (unquote height)) (weight . (unquote weight)) (carbon-content . (unquote carbon-content))))) (tree-quasiquote "birch" 13 90 45) (define (tree-shorthand type height weight carbon-content) "Create a tree with quasiquote/unquote shorthands." `((type . ,type) ;; ` is short for (quasiquote) (height . ,height) ;; , is short for (unquote) (weight . ,weight) (carbon-content . ,carbon-content))) (display (tree-shorthand "birch" 13 90 45))
((type . birch) (height . 13) (weight . 90) (carbon-content . 45))
These three methods are almost equivalent, except that quasiquoting can create an immutable list, but does not have to.
(define three 3) (define mutable-list (list 1 2 3)) (list-set! mutable-list 1 'a) ;; zero-indexed mutable-list
(1 a 3)
(define immutable-list `(1 2 3)) (list-set! immutable-list 1 'a) ;; error! immutable-list
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure set-car!: Wrong type argument in position 1 (expecting mutable pair): (2 3)
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
(define three 3) (define mutable-quasiquoted `(1 2 ,three)) (list-set! mutable-quasiquoted 1 'a) ;; no error yet! mutable-quasiquoted
(1 a 3)
Mutating quasiquoted lists may always throw an error in the future:
A quasiquote expression may return either newly allocated, mutable objects or literal structure for any structure that is constructed at run time … - the standard (r7rs)
Merge lists with append or unquote-splicing
(import (ice-9 pretty-print)) (define birch-carbon/kg '(5000 5301 5500)) (define oak-carbon/kg '(7000 7700 8000)) ;; append merges lists (pretty-print (append birch-carbon/kg oak-carbon/kg)) ;; unquote-splicing splices a list into quasiquote (`) (pretty-print `((unquote-splicing birch-carbon/kg) (unquote-splicing oak-carbon/kg))) ;; with shorthand ,@ (pretty-print `(,@birch-carbon/kg ,@oak-carbon/kg))
(5000 5301 5500 7000 7700 8000) (5000 5301 5500 7000 7700 8000) (5000 5301 5500 7000 7700 8000)
Unquote splicing can also insert the result of logic:
`(,@(map 1- '(1 2 3)) (unquote-splicing (map 1+ (reverse '(1 2)))) ,@(list 1 0))
(0 1 2 3 2 1 0)
Document procedures with docstrings
(define (documented-proc arg) "Procedure is documented" #f) ;; doc must not be the last element (display (procedure-documentation documented-proc)) (newline) ;; variables have no docstrings but the documentation ;; property can be set manually. (define variable #f) (set-object-property! variable 'documentation "Variable is documented") (display (object-property variable 'documentation))
Procedure is documented Variable is documented
You can get the documentation with help or ,d on the REPL:
,d documented-proc => Proc is documented ,d variable => Variable is documented
For generating documentation from comments, there’s guild doc-snarf.
;; Procedure documentation can be snarfed (define (snarfed-proc arg) #f) ;; Variable documentation can be snarfed (define snarfed-variable #f)
If this is saved as hello.scm, get the docs via
guild doc-snarf --texinfo hello.scm
Read the docs
Now you understand the heart of code. With this as the core there is one more step, the lifeblood of programming: learning more. Sources:
- Guile Reference manual
- Guile Library
- Scheme Requests for Implementation (SRFI): tagged libraries
- Scheme standards (RnRS), specifically r7rs-small (pdf)
- A list of tools and libraries
- Rosetta Code with solutions to many algorithm problems
Info manuals can often be read online, but the info commandline
application and info in Emacs (C-h i) are far more efficient and
provide full-text search. You can use them to read the Guile reference
manual and some libraries. Get one by installing texinfo or Emacs.
In interactive guile (the REPL), you can check documentation:
(help string-append)
`string-append' is a procedure in the (guile) module.
- Scheme Procedure: string-append . args
Return a newly allocated string whose characters form the
concatenation of the given strings, ARGS.
,help
Help Commands [abbrev]: ...
Create a manual with texinfo
Create a doc/ folder and add a hello.texi file.
An example file can look like the following:
@documentencoding UTF-8 @settitle Hello World @c This is a comment; The Top node is the first page @node Top @c Show the title and clickable Chapter-names as menu @top @menu * First Steps:: * API Reference:: @end menu @contents @node First Steps @chapter First Steps @itemize @item Download from ... @item Install: @code{make}. @end itemize Example: @lisp (+ 1 2) @result{} 3 @end lisp @node API Reference @chapter API Reference @section Procedures @subsection hello Print Hello @example hello @end example
Add a Makefile in the doc/ folder:
all: hello.info hello.epub hello_html/index.html hello.info: hello.texi makeinfo hello.texi hello.epub: hello.texi makeinfo --epub hello.texi hello_html/index.html: hello.texi makeinfo --html hello.texi
Run make in the doc/ folder:
make
Read the docs with calibre or the browser or plain info:
calibre hello.epub & \ firefox hello_html/index.html & \ info -f ./hello.info
The HTML output is plain. You can adapt it with CSS by adding
--css-include=FILENAME or --css-ref=URL.
Alternately you can write an Org Mode document and evaluate
(require 'ox-texinfo) to activate exporting to texinfo.
Track changes with a version tracking system like Mercurial or Git
For convenience, first initialize a version tracking repository like Mercurial or Git.
# either Mercurial hg init hello # or Git git init hello # enter the repository folder cd hello/
Now you can add new files with
# in Mercurial hg add FILE # in Git git add FILE
And take a snapshot of changes with
# in Mercurial hg commit -m "a change description" # in Git git commit -a -m "a change description"
It is good practice to always use a version tracking system.
For additional information and how to compare versions, go back in time, or publish your code if you want to, see the Mercurial Guide or the Git Tutorial.
Package with autoconf and automake
Create a configure.ac file with name, contact info and version.
dnl configure.ac dnl Name, Version, and contact information. AC_INIT([hello], [0.0.1], [myName@example.com]) # Set a supported Guile version as @GUILE@, then init GUILE_PKG([3.0]) GUILE_PROGS GUILE_SITE_DIR AC_PREFIX_PROGRAM([guile]) AM_INIT_AUTOMAKE([gnu]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT
Add a Makefile.am with build rules. Only the start needs to be edited:
dist_bin_SCRIPTS = hello # program name SUFFIXES = .scm .sh SCHEME = hello.scm # source files hello: $(SCHEME) echo "#!/usr/bin/env bash" > "$@" && \ echo 'exec -a "$$0" guile' \ '-L "$$(dirname "$$(realpath "$$0")")"' \ '-L "$$(dirname "$$(realpath "$$0")")/../share/guile/site/3.0/"' \ '-s "$$0" "$$@"' \ >> "$@" && echo ";; exec done: !#" >> "$@" && \ cat "$<" >> "$@" && chmod +x "$@" TEXINFO_TEX = doc/hello.texi # else it needs texinfo.texi info_TEXINFOS = doc/hello.texi # add library files, prefix nobase_ preserves directories nobase_site_DATA =
The rest of the Makefile.am can be copied verbatim:
## Makefile.am technical details # where to install guile modules to import. See # https://www.gnu.org/software/automake/manual/html_node/Alternative.html sitedir = $(datarootdir)/guile/site/$(GUILE_EFFECTIVE_VERSION) GOBJECTS = $(nobase_site_DATA:%.scm=%.go) nobase_go_DATA = $(GOBJECTS) godir=$(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccachep # Make sure that the mtime of installed compiled files # is greater than that of installed source files. See: # http://lists.gnu.org/archive/html/guile-devel/2010-07/msg00125.html # The missing underscore before DATA is intentional. guile_install_go_files = install-nobase_goDATA $(guile_install_go_files): install-nobase_siteDATA EXTRA_DIST = $(SCHEME) $(info_TEXINFOS) $(nobase_site_DATA) CLEANFILES = $(GOBJECTS) $(wildcard *~) $(dist_bin_SCRIPTS) DISTCLEANFILES = $(nobase_site_DATA) # precompile all source files .scm.go: $(GUILE_TOOLS) compile $(GUILE_WARNINGS) \ -o "$@" "$<"
Autoconf and automake are easy to use for distros.
This assumes that the folder hello uses a Version tracking system.
## Makefile.am basic additional files .SECONDARY: ChangeLog AUTHORS ChangeLog: ## create the ChangeLog from the history echo "For user-visible changes, see the NEWS file" > "$@" echo >> "$@" if test -d ".hg"; \ then hg -R "$(dirname "$(realpath .hg)")" \ log --style changelog \ >> "$@"; fi if test -d ".git"; \ then cd "$(dirname "$(realpath .git)")" \ && git log --date-order --date=short \ | sed -e '/^commit.*$/d' \ | awk '/^Author/ {sub(/\\$/,""); getline t; print $0 t; next}; 1' \ | sed -e 's/^Author: //g' \ | sed -e \ 's/\(.*\)>Date: \([0-9]*-[0-9]*-[0-9]*\)/\2 \1>/g' \ | sed -e 's/^\(.*\) \(\)\t\(.*\)/\3 \1 \2/g' \ >> "$@"; cd -; fi AUTHORS: ## create the AUTHORS file from the history if test -d ".hg"; \ then hg -R "$(dirname "$(realpath .hg)")" \ --config extensions.churn= \ churn -t "{author}" >> "$@"; fi if test -d ".git"; \ then cd "$(dirname "$(realpath .git)")" \ && git log --format='%aN' \ | sort -u >> "$@"; cd -; fi
Now create a README and a NEWS file:
#+title: Hello A simple example project. * Requirements - Guile version 3.0.10 or later. * Build the project #+begin_src bash autoreconf -i && ./configure && make #+end_src * Create a distribution tarball #+begin_src bash autoreconf -i && ./configure && make dist #+end_src * License GPLv3 or later.
hello 0.0.1 - initialized the project
And for the sake of this example, a simple hello.scm file:
(display "Hello World!\n")
Init a project with hall
To simplify the setup, start it by getting the tool guile-hall (named Hall) as described in the manual under Distributing Guile Code. Then create a new project:
hall init hello -a "My Name" \ -e "myName@example.com" \ --execute cd hello && hall build --execute
Add --license to change the license; GPLv3 or later is the default.
Hall creates a configure.ac file with name, contact information and
version, and a Makefile.am with build rules. It also automatically
adds TEXINFO-rules for the folder doc/.
Deploy a project to users
Enable people to access your project as a webserver behind nginx, as clientside browser-app, or as Linux package (Guix tarball).
Browser: as webserver. On the web no one knows you’re a Scheme.
Guile provides a webserver module. A minimal webserver:
(import (web server) (web request) (web response) (web uri)) (define (handler request body) (define path (uri-path (request-uri request))) (values (build-response #:headers `((content-type . (text/plain))) #:code 404) (string-append "404 not found: " path))) (define v4 #t) ;; choose either IPv4 or IPv6; to suport both, run twice. (run-server handler 'http (if v4 '(#:port 8081) '(#:family AF_INET6 #:port 8081)))
An nginx SSL Terminator (/etc/nginx/sites-enabled/default):
server {
server_name domain.example.com;
location / {
proxy_pass http://localhost:8081;
}
}
Set up SSL support with certbot (this edits the config file).
Browser again: clientside wasm. To run clientside, you can package your project with Hoot: build an interface and compile to wasm:
;; file: hoot.scm (use-modules (hoot ffi)) ;; hoot-specific import ;; the interface (define-foreign document-body "document" "body" -> (ref null extern)) (define-foreign make-text-node "document" "createTextNode" (ref string) -> (ref null extern)) (define-foreign append-child! "element" "appendChild" (ref null extern) (ref null extern) -> (ref null extern)) ;; core code (append-child! (document-body) ;; Your logic (make-text-node "Hello, world!"))
Transpile with guild compile-wasm. If you run Guix:
guix shell guile-hoot guile-next -- \
guild compile-wasm -o hoot.wasm hoot.scm
Get reflection tools from Guile Hoot (licensed Apache 2.0) with Guix:
guix shell guile-hoot guile-next -- bash -c \ 'cp $GUIX_ENVIRONMENT/share/guile-hoot/*/reflect*/{*.js,*.wasm} ./'
Load your interface:
/* file: hoot.js */ window.addEventListener("load", () => Scheme.load_main("./hoot.wasm", { user_imports: { /* the interface from Javascript */ document: { body() { return document.body; }, createTextNode: Document.prototype .createTextNode.bind(document) }, element: { appendChild(parent, child) { return parent.appendChild(child);}}}}));
Include reflect.js and hoot.js from a HTML page:
<!DOCTYPE html> <!-- file: hoot.html --> <html><head><title>Hello Hoot</title> <script type="text/javascript" src="reflect.js"></script> <script type="text/javascript" src="hoot.js"></script> </head><body><h1>Hoot Test</h1></body></html>
Have a look at hoot.html to see the result.
For local testing, hoot provides a minimal webserver:
guix shell guile-hoot guile-next -- \ guile -c '((@ (hoot web-server) serve))'
Linux: Guix tarball. The package is the tarball. — Ludovic
Guix can assemble a tarball of all dependencies. Add a guix.scm:
(import (gnu packages web) (gnu packages base) (gnu packages bash) (gnu packages guile) (gnu packages guile-xyz) (gnu packages pkg-config) (guix packages) (guix gexp) (guix build-system gnu) (guix build-system guile) (prefix (guix licenses) license:)) (define-public guile-doctests (package (name "guile-doctests") (version "0.0.1") (source (local-file "." "" #:recursive? #t)) (build-system gnu-build-system) ;; uses autoconf and automake (arguments (list #:modules `((guix build guile-build-system) ,@%default-gnu-imported-modules) #:phases (with-imported-modules `((guix build guile-build-system) ,@%default-gnu-imported-modules) #~(modify-phases %standard-phases (add-after 'install 'wrap (lambda* (#:key inputs outputs #:allow-other-keys) (let ((out (assoc-ref outputs "out")) (effective-version (target-guile-effective-version))) (wrap-program (string-append out "/bin/doctest") `("PATH" ":" prefix ,(map (λ (x) (dirname (search-input-file inputs x))) (list "/bin/guile" "/bin/bash"))) `("GUILE_LOAD_PATH" prefix (,(string-append out "/share/guile/site/" effective-version) ,(getenv "GUILE_LOAD_PATH"))) `("GUILE_LOAD_COMPILED_PATH" prefix (,(string-append out "/lib/guile/" effective-version "/site-ccache") ,(getenv "GUILE_LOAD_COMPILED_PATH"))))))))))) (propagated-inputs `(("guile" ,guile-3.0) ("pkg-config" ,pkg-config) ("bash" ,bash) ("guile-wisp" ,guile-wisp) ("coreutils" ,coreutils))) (home-page "https://hg.sr.ht/~arnebab/guile-doctests") (synopsis "Tests in procedure definitions") (description "Guile module to keep tests in your procedure definition.") (license license:lgpl3+))) guile-doctests
First test building guix build -f guix.scm, then test running with
guix shell --pure -f guix.scm and once both work, create your
package with:
guix pack -e '(load "guix.scm")' \ -RR -S /bin=bin -S /share=share
Copy the generated tarball. In can be executed with:
mkdir hello && cd hello && tar xf TARBALL_FILE && \ ./bin/doctest
Since this tarball generation is a bit finicky, there is a guile-doctests package with a working example setup.
Once you have guix pack working, you can also create dockerfiles
and other packages to deploy into various publishing infrastructures.
To be continued: Scheme is in constant development and deploying Guile programs is getting easier. Lilypond solved Windows.
Also see the Map of R7RS and the Scheme primer to keep learning.
You are ready.
Go and build a project you care about.
