Published on july 2, 2019 at starynkevitch.net/Basile/guile-tutorial-2.html. this is work in progress and incomplete.
Refer also to the R5RS and its index, and to the Guile reference and tutorial.
In Scheme, Guile, and
all Lisp
dialects (including Common
Lisp, E-Lisp, Clojure,
etc...), symbols
are first-class values, and the reader component
of guile
manages
a symbol
table to keep them. Conceptually, symbols always
exist. Practically, the guile
interpreter would add a
symbol to its symbol table when reading its first occurrence, and
keep there that symbol indefinitely, until it exits. You can input a
symbol as an identifier such
as fact, foo-bar
or even +. When some symbol is input
twice, the reader is parsing it twice as the same and
identical symbol value.
Beware that some languages, notably JavaScript, use the symbol terminology with a different meaning than Lisp dialects, but inspired by it.
Since Guile symbols are values, you can get a symbol from its string
name using string->symbol
,
so (string->symbol
"x") → x,
and if x
was previously known, that same symbol is
returned. That same string->symbol
primitive is even
usable to obtain a symbol difficult to type in usual Guile
syntax, e.g. (string->symbol
"(a") → #{\x28;a}#
which is still a weird-looking symbol. The reverse operation,
getting as a string the name of a symbol, is of
course symbol->string
. You can also create
a fresh symbol, different of all previously known ones,
using gensym
to guile
, but the next time the same expression is
evaluated, it might give x438. Of
course, the numbers 391 or 438 suffixing these generated symbols are
unique and implementation-specific, and you are likely to get
different ones.
Symbols are often used
as lookup
keys in hash tables or a-lists, and that explains a bit why Lisp
or Guile excells
in symbolic
computation such
as expert
systems shells
(re-invented today
as business
rule management
systems), theorem
provers
or proof
assistants,
or discovery
systems such
as Eurisko
or Cyc. Notice some
similarity of symbols, when used as keys, with JavaScript
objects' fields, because in JavaScript ob.field
is defined to be the same
as ob['field']
. However, what
JavaScript calls symbols is quite different of
symbols in Scheme.
Symbols in Guile are actually quite complex -or at least
composite- values with some semi-hidden content: they
each have
their property list (and this
is inspired
by Lisp). That property list of a symbol s is
associating symbols to their corresponding value, and is generally
implemented as some a-list specific to that s. But
property lists tend to become deprecated in Guile. Use the
binary (symbol-property symb prop)
primitive to get in symbol symb the property given by
symbol prop. Use (set-symbol-property! symb prop val)
to put in symbol symb the property prop associated
to value val. To remove from symbol symb the
property prop,
code (symbol-property-remove! symb prop)
. Using
such arbitrary properties could be useful
in frame-based
applications, if every frame would be represented
by gensym
-ed symbols. But recent Guile
is proposing a more
general object
system facilitating such applications.
Since, as an expression, a
symbol denotes a variable and is evaluated into the bound value of
that variable. So a special syntax is required to get a symbol
value without evaluating the variable denoted by it. The quote
operator provides it, so (quote foo) → foo. That operator is so common that a syntactic sugar provides a shorter notation, so (quote foo) can be abreviated as 'foo and both are exactly equivalent.
The quote operator can even be applied to an entire sub-expression, giving the list representing it. So (quote (+ 2 x)) or '(+ 2 x) → (+ 2 x) which is a list of three elements whose first one is the symbol +
. You could build the same list using (list '+ 2 'x) and this illustrates that Scheme is homoiconic.
The "reciprocal" of quote
is the eval
primitive,
the evaluation
operator. It takes two evaluated operands, the expression to be
(re-)evaluated and (optionally) the environment -binding symbols
denoting variables to their value- in which you would evaluate
it. So (eval '(+ 2
3)) → 5
and if some-number
has been define
-d as
5, (eval '(* 2 some-number)
(interaction-environment))→ 10,
since interaction-environment
obviously gives your
current interaction environment as a first-class value. Notice
that eval
is rarely useful in practice,
because Scheme has better
metaprogramming
facilities.
As
explained before, the
[linked] list
is a major data type of Scheme and is built
from pairs. The
empty list is called nil and
written () (and,
counter-intuitively, Scheme considers nil as a true
value in tests
inside conditional
expressions). Notice that lists are constructed
from pairs,
and that expressions are represented by lists, but arbitrary pairs
are generally not forming a proper list, so are not always valid
expressions. You can use the dotted pair syntax to make the reader
parse a pair - usually as a quoted thing -, for
example '(1 . a). Of
course pair?
is the predicate testing if some value is
a pair, and list?
is the predicate testing if that
value is a proper list made from pairs, the last ending
with nil. A pair contains exactly two values:
its head, also called its car
, and
its tail, also called its cdr
(pronounced
"kooder"). So (car '(1 . a))→ 1 and (cdr '(1 . a))→ a.
Composing these primitives is so common that several abbreviations
are available, e.g. (cadr l) is equivalent to (car (cdr l)) and (cdadr l) is equivalent to (cdr (c ar (cdr l))), etc....
The car
and cdr
names are historical and relate to registers of the IBM 704 computer first running -in the late 1950s- some Lisp.
To make a pair from its head and tail, use the
binary cons
primitive (as "construct a
pair"). To make a list from its elements, use the
variadic list
primitive. So (list
23 'a
"str") → (23
a "str") and, since it is a linked list of pairs, could
have been obtained from (cons 23 (cons 'a
(cons "str" ()))). Of course (car (list
23
'a)) → 23
and (cdr (list 23 'a
"str")) → (a
"str").
Pairs can even be mutated in place using set-car!
to
change the head, and set-cdr!
to change their
tail.
So (let ( (l
(list 'a 'b)) ) (pretty-print
l) (set-car! l 'c)
l) → (c
b) after having pretty-printed (a
b). However, the Guile interpreter is reading
expressions as lists, but their in-place mutation is forbidden,
so (set-car! '(a b) 3)
is wrong and fails to evaluate.
The map
binary primitive takes a procedure and a list and returns the list
made by applying that procedure to each element of the second
argument, the initial list. So (map
(lambda (x) (* x 2)) (list 3 5
10))→ (6
10 20).
In Scheme, vector values are like arrays in many
other languages, and their size is fixed, while their content is
mutable and may contain arbitrary values. In particular, they are
not resizable
as C++ std::vector
-s
are. See
also Guile' section
on vectors
and R5RS
section on vectors.
To test if a value is a vector, use the
unary vector?
primitive. To create a mutable vector
from its elements, use the variadic vector
primitive, so (vector 1 'a
"str") → #(1
a "str"). To create a mutable vector of run-time known size
with all elements initialized to the same value,
use make-vector
, for
example (make-vector (+ 2 3)
'x)→ #(x
x x x x).
The #( vector-elements ... ) notation can be even used at REPL time, so you can enter a literal vector constant as #(1 2 3) and that literal is, like string literals are, self-quoting (e.g. evaluates to itself). However, in Scheme such literal vector constants are immutable, for the same reasons literal strings or literal integer constants are immutable : since you don't want the behavior of your code to change during run-time, and some clever Scheme compilers would place such literal constants in the read-only code segment of the produced executable.
To get the length of a vector, use vector-length
so
(vector-length #(1 a))
→ 2.
To get some n-th element,
use vector-ref
like
(vector-ref #(1 a (+ b 3) "str") 2)
→ (+ b 3).
To change some n-th element,
use vector-set!
so
(let ( (v (vector 'a 1 "str")) ) (vector-set! v 1 'b) v)
→ #(a b "str").