Miscellaneous
Buffered channels
To use the bindings from this module:
(import :std/misc/channel)
make-channel
(make-channel [n = #f]) -> channel
n := optional fixnum, number of values channel buffer can hold
Creates a new buffered channel, a synchronization construct useful for sending
and receiving data between producers and consumers, implicitly locking when
reading from or writing to the channel. Chaining multiple channels, one after
another, allows building computation pipelines with ease. n specifies how many
elements the channel buffer is allowed to hold before blocking, with #f
never
blocking at all.
Examples:
> (import :std/iter :gerbil/gambit/threads)
> (def (consume ch)
(let (val (channel-get ch))
(unless (eof-object? val)
(with ([src . num] val)
(displayln "received " num " from " src)
(consume ch)))))
> (def (produce ch count)
(for (i (in-iota count))
(channel-put ch (cons (current-thread)
(+ 10 (random-integer 90))))))
> (let* ((ch (make-channel 2))
(consumer (spawn consume ch))
(producers (for/collect (i (in-iota 3))
(spawn produce ch 3))))
(for-each thread-join! producers)
(channel-close ch)
(thread-join! consumer))
received 36 from #<thread #4>
received 73 from #<thread #4>
received 69 from #<thread #4>
received 73 from #<thread #5>
received 52 from #<thread #5>
received 59 from #<thread #5>
received 69 from #<thread #6>
received 53 from #<thread #6>
received 81 from #<thread #6>
channel?
(channel? ch) -> boolean
ch := channel to check
Returns #t
if ch is a channel, #f
otherwise.
Examples:
> (channel? (make-channel))
#t
> (make-channel 3)
#<channel #26>
> (channel-close #26)
> (channel? #26)
#t
channel-put
(channel-put ch val [timeout = #f]) -> boolean | error
ch := channel to write to
val := value to write into ch
timeout := optional, how long to wait when channel is full
Writes val to ch and returns a truth value indicating whether the send was
successful or not. channel-put
blocks when the channel's buffer is full,
waiting indefinitely for an available slot or until the optional timeout,
declared in seconds or as a relative time object, is reached. Sending data to an
already closed channel will signal an error.
Examples:
> (def ch (make-channel 3))
> (channel-put ch 'a)
#t
> (channel-put ch 'b)
#t
> (channel-put ch 'c)
#t
> (channel-put ch 'd 2) ; buffer full, gives up after 2 seconds
#f
> (import :gerbil/gambit/threads)
> (spawn-thread (lambda () (thread-sleep! 2) (channel-get ch)))
#<thread #29>
> (channel-put ch 'e) ; blocks until other thread retrieves value
#t
> (channel-put ch 'e) ; blocks indefinitely, no other threads retrieve values
*** ERROR IN ##thread-deadlock-action! -- Deadlock detected
channel-try-put
(channel-try-put ch val) -> boolean | error
ch := channel to write to
val := value to write into ch
Similar to channel-put
, but doesn't block when the channel's buffer is full,
simply indicating success or failure via a truth value. Sending data to an
already closed channel will signal an error.
Examples:
> (def ch (make-channel 3))
> (channel-try-put ch 'a)
#t
> (channel-try-put ch 'b)
#t
> (channel-try-put ch 'c)
#t
> (channel-try-put ch 'd) ; buffer full, doesn't block, gives up right away
#f
> (close-channel ch)
> (channel-try-put ch 'e) ; channel already closed, no longer valid to right to it
error
channel-sync
(channel-sync ch val ...) -> void | error
ch := channel to write to
val ... := values to send to ch
Forcefully writes val ... to ch, ignoring the channel's buffer limit. Useful for sending special values that indicate a bi-directional out-of-band communication request between consumers and producers without blocking. Sending data to an already closed channel will signal an error.
Examples:
> (import :std/iter :gerbil/gambit/threads)
> (def (consume ch)
(let loop ((i 0))
(match (channel-get ch)
((eq? #!eof)
(displayln "we're done here"))
((cons (quote more?) (? thread? thread))
(displayln "received: sync request")
(thread-send thread (if (< i 10) 'yes 'no))
(loop i))
((? number? num)
(displayln "received: " num)
(loop (1+ i))))))
> (def (produce ch)
(let loop ()
(if (channel-try-put ch (random-integer 100))
(loop)
(begin ; if buffer full, ask consumer whether to produce more
(channel-sync ch (cons 'more? (current-thread)))
(match (thread-receive)
('yes (loop))
('no (channel-close ch)))))))
> (let* ((ch (make-channel 4))
(producer (spawn produce ch))
(consumer (spawn consume ch)))
(for-each thread-join! [producer consumer]))
received: 28
received: 67
received: 79
received: 67
received: sync request ; out-of-band answer via thread-send: 'yes
received: 21
received: 43
received: 71
received: 54
received: sync request ; answer: 'yes
received: 29
received: 19 ; consumer processed 10 items, target reached
received: 61
received: 88
received: sync request ; answer: no; producer closes channel, consumer shuts down
we're done here
channel-get
(channel-get ch [timeout = #f] [default = #f]) -> any | default | #!eof
ch := channel to read from
timeout := optional, how long to wait when channel is empty
default := optional, value to return when timeout reached
Reads a value from ch and returns it, or default if a timeout was set and
reached, declared in seconds or as a relative time object. channel-get
blocks
when the channel's buffer is empty, waiting indefinitely for more data or until
the optional timeout is reached. Reading data from an already closed channel
is allowed, but will always return #!eof
.
Examples:
> (def ch (make-channel 3))
> (for-each (cut channel-try-put ch <>) (iota 10))
> (channel-get ch)
0
> (channel-get ch 2) ; returns right away
1
> (channel-get ch 2 'EMPTY)
2
> (channel-get ch 2) ; channel can only hold three values, waits two seconds
#f
> (channel-get ch 2 'EMPTY)
EMPTY
> (channel-close ch)
> (channel-get ch)
#!eof
> (channel-get ch 2 'EMPTY) ; closed channel always returns #!eof
#!eof
channel-try-get
(channel-try-get ch [default = #f]) -> any | default | #!eof
ch := channel to read from
default := optional, value to return when channel empty
Similar to channel-get
, but doesn't block when the channel's buffer is empty,
simply returning default in that case. Reading data from an already closed
channel is allowed, but will always return #!eof
.
Examples:
> (def ch (make-channel 3))
> (for-each (cut channel-try-put ch <>) (string->list "abcdef"))
> (channel-try-get ch)
#\a
> (channel-try-get ch)
#\b
> (channel-try-get ch 'EMPTY)
#\c
> (channel-try-get ch 'EMPTY) ; returns right away, no blocking occurs
EMPTY
> (channel-close ch)
> (channel-try-get ch 'EMPTY)
#!eof
Channel Iterator
(defmethod (:iter (ch channel)) (iter-channel ch)) -> iterator
ch := channel to iterate over
The module defines a generic dispatch overload for buffered channels, allowing them to be iterated just like lists or hashes. Iterating ch will yield values, and block if necessary, until the channel is closed and its elements fully consumed.
Examples:
> (import :std/iter :gerbil/gambit/threads)
> (def (consume ch)
(for/fold (sum 0) (x ch)
(+ x sum)))
> (def (produce ch count)
(for (i (in-iota count))
(channel-put ch (random-integer 100))))
> (let* ((ch (make-channel))
(consumer (spawn consume ch))
(producers (for/collect (i (in-iota 10))
(spawn produce ch 100))))
(for-each thread-join! producers)
(channel-close ch)
(thread-join! consumer))
49644
channel-close
(channel-close ch) -> void
ch := channel to close
Closes a buffered channel, forbidding write access. Consumers are still allowed
to retrieve values from such a closed channel, but once empty, it will simply
return #!eof
.
Note: Only senders should close channels. Furthermore, it's not an error to close a channel multiple times.
Examples:
> (def ch (make-channel))
> (channel-put ch 1)
#t
> (channel-close ch)
> (channel-get ch) ; reading from a closed channel is allowed
1
> (channel-get ch)
#!eof
> (channel-put ch 2) ; writing to a closed channel signals an error
error
channel-closed?
(channel-closed? ch) -> boolean
ch := channel to check
Returns #t
if ch is closed, #f
otherwise.
Examples:
> (def ch (make-channel))
> (channel-closed? ch)
#f
> (channel-close ch)
> (channel-closed? ch)
#t
Channel Destructor
(defmethod {destroy <port>} channel-close)
The module also defines a destroy
method for channels, so that they can be
used in with-destroy
forms and other primitives that use the destroy idiom,
ensuring that channels will be closed even if an error is signaled somewhere
within the body.
Examples:
> (def ch (make-channel))
> (channel-put ch 10)
#t
> (channel-closed? ch)
#f
> (with-destroy ch
(channel-get ch))
10
> (channel-closed? ch)
#t
Bytes
This library provides primitives for operating on u8vectors as well as some utility functions.
To use the bindings from this module:
(import :std/misc/bytes)
endianness
(endianness big|little|native) -> endianness symbol
(def big 'big)
(def little 'little)
(def native 'native)
(def native-endianness ...)
Specifies the endianness for integer and floating point operations on u8vectors.
The supported endianness can be big
, little
, or native
; native-endianness
is bound
at runtime to the native architecture endianness.
u8vector-s8-ref
(u8vector-s8-ref v i) -> int
v := u8vector
i := integer; offset index in v
Retrieves the signed byte from v in position i, decoding from a 2's complement representation.
u8vector-s8-set!
(u8vector-s8-set! v i n) -> unspecified
v := u8vector
i := integer; offset index in v
n := signed byte
Sets the signed byte in v at position i from the value n, encoding to 2's complement representation.
u8vector-uint-ref
(u8vector-uint-ref v i endianness size) -> exact nonnegative integer
v := u8vector
i := integer; offset index in v
endianness := endianness symbol; the endianness of the binary representation of the integer
size := integer; the size of the integer in bytes
Retrieves an unsigned integer from its binary representation from v, starting at index i.
u8vector-uint-set!
(u8vector-uint-set! v i n endianness size) -> unspecified
v := u8vector
i := integer; offset index in v
n := exact nonnegative integer; the value to set
endianness := endianness symbol; the endianness of the binary representation of the integer
size := integer; the size of the integer in bytes
Encodes the unsigned integer n in its binary representation in v, starting at offset i.
u8vector-sint-ref
(u8vector-sint-ref v i endianness size) -> exact integer
v := u8vector
i := integer; offset index in v
endianness := endianness symbol; the endianness of the binary representation of the integer
size := integer; the size of the integer in bytes
Retrieves a signed integer from its binary representation from v, starting at index i.
u8vector-sint-set!
(u8vector-sint-set! v i n endianness size) -> unspecified
v := u8vector
i := integer; offset index in v
n := exact integer; the value to set
endianness := endianness symbol; the endianness of the binary representation of the integer
size := integer; the size of the integer in bytes
Encodes the signed integer n in its binary representation in v, starting at offset i.
u8vector->uint-list
(u8vector->uint-list v endianness size) -> list of exact nonnegative integers
v := u8vector
endianness := endianness symbol; the endianness of the binary representation of each integer
size := integer; the size of each integer in bytes
Decodes a u8vector v into a list of exact nonnegative integers.
uint-list->u8vector
(uint-list->u8vector lst endianness size) -> u8vcetor
lst := list of exact nonnegative integers
endianness := endianness symbol; the endianness of the binary representation of each integer
size := integer; the size of each integer in bytes
Encodes a list of unsigned integers to a u8vector.
u8vector->sint-list
(u8vector->sint-list v endianness size) -> list of exact integers
v := u8vector
endianness := endianness symbol; the endianness of the binary representation of each integer
size := integer; the size of each integer in bytes
Decodes a u8vector v into a list of exact integers.
sint-list->u8vector
(uint-list->u8vector lst endianness size) -> u8vector
lst := list of exact integers
endianness := endianness symbol; the endianness of the binary representation of each integer
size := integer; the size of each integer in bytes
Encodes a list of signed integers to a u8vector.
Operations on Machine-size Integers
(u8vector-u16-ref v i endianness) -> u16
(u8vector-u16-native-ref v i) -> u16
(u8vector-u16-ref-set! v i n endianness) -> unspecified
(u8vector-u16-native-set! v i n) -> unspecified
(u8vector-s16-ref v i endianness) -> s16
(u8vector-s16-native-ref v i) -> s16
(u8vector-s16-ref-set! v i n endianness) -> unspecified
(u8vector-s16-native-set! v i n) -> unspecified
(u8vector-u32-ref v i endianness) -> u32
(u8vector-u32-native-ref v i) -> u32
(u8vector-u32-ref-set! v i n endianness) -> unspecified
(u8vector-u32-native-set! v i n) -> unspecified
(u8vector-s32-ref v i endianness) -> s32
(u8vector-s32-native-ref v i) -> s32
(u8vector-s32-ref-set! v i n endianness) -> unspecified
(u8vector-s32-native-set! v i n) -> unspecified
(u8vector-u64-ref v i endianness) -> u64
(u8vector-u64-native-ref v i) -> u64
(u8vector-u64-ref-set! v i n endianness) -> unspecified
(u8vector-u64-native-set! v i n) -> unspecified
(u8vector-s64-ref v i endianness) -> s64
(u8vector-s64-native-ref v i) -> s64
(u8vector-s64-ref-set! v i n endianness) -> unspecified
(u8vector-s64-native-set! v i n) -> unspecified
Operations for encoding and decoding machine-sized integers. When the endianness matches the native endianness, then a faster native implementation is used.
Operations on Floating Point Numbers
(u8vector-float-ref v i endianness) -> flonum
(u8vector-float-native-ref v i) -> flonum
(u8vector-float-set! v i x endianness) -> unspecified
(u8vector-float-native-set! v i x) -> unspecified
(u8vector-double-ref v i endianness) -> flonum
(u8vector-double-native-ref v i) -> flonum
(u8vector-double-set! v i x endianness) -> unspecified
(u8vector-double-native-set! v i x) -> unspecified
Operations for encoding and decoding floating point numbers using the IEEE-754 representation.
u8vector-swap!
(u8vector-swap! v i j) -> unspecified
v := u8vector
i := integer element position
j := integer element position
Swaps elements i and j of u8vector
v.
Examples:
> (def u (u8vector 1 2))
> (u8vector-swap! u 0 1)
> u
#u8(2 1)
u8vector-reverse!
(u8vector-reverse! v) -> void
v := u8vector
Reverses the elements of u8vector
v in-place. Mutates the vector.
Examples:
> (def u (u8vector 1 2 3 4 5))
> (u8vector-reverse! u)
> u
#u8(5 4 3 2 1)
u8vector-reverse
(u8vector-reverse v) -> u8vector
v := u8vector
Reverses the elements of u8vector
v. Produces a new u8vector
.
Examples:
> (def u (u8vector 1 2 3 4 5))
> (u8vector-reverse u)
#u8(5 4 3 2 1)
u8vector->bytestring
(u8vector->bytestring v (delim #\space)) -> bytestring
v := u8vector
delim := char
Constructs a string of bytes in hexadecimal from u8vector
v.
Each byte is formatted as two uppercase hex characters
and separated using the specified delimiter character; the delimiter can
be #f
to specify simple concatenation.
Examples:
> (u8vector->bytestring (u8vector 255 127 11 1 0))
"FF 7F 0B 01 00"
> (displayln (u8vector->bytestring (u8vector 255 127 11 1 0)))
FF 7F 0B 01 00
> (u8vector->bytestring (u8vector 1 2 3) #f)
"010203"
bytestring->u8vector
(bytestring->u8vector bs (delim #\space)) -> u8vector
bs := bytestring
delim := char
Constructs a u8vector
from bytestring bs.
This function expects a string of bytes delimited by delim, which can be #f
to indicate
no delimiter. Each byte consists of two hexadecimal characters.
Examples:
> (bytestring->u8vector "FF AB 00")
#u8(255 171 0)
> (u8vector->bytestring (bytestring->u8vector "FF AB 00"))
"FF AB 00"
> (string->bytes "FF AB 00")
#u8(70 70 32 65 66 32 48 48)
u8vector->uint
(u8vector->uint v (endianness big)) -> uint
v := u8vector
endianness := endianness symbol
Decodes a u8vector as an unsigned integer.
uint->u8vector
(uint->u8vector n (endianness big)) -> u8vector
n := exact nonnegative integer
endianness := endianness symbol
Encodes an unsigned integer to its binary representation.
Examples:
> (u8vector->uint #u8(0 1))
1
> (u8vector->uint (make-u8vector 2 #xFF))
65535
> (u8vector->uint (make-u8vector 8 #xFF))
18446744073709551615
> (equal? (- (expt 2 64) 1) (u8vector->uint (make-u8vector 8 #xFF)))
#t
Aliases
The following aliases are defined, using the canonical Scheme naming of u8vectors as bytevectors.
(defalias bytevector-u8-ref u8vector-ref)
(defalias bytevector-u8-set! u8vector-set!)
(defalias bytevector-s8-ref u8vector-s8-ref)
(defalias bytevector-s8-set! u8vector-s8-set!)
(defalias bytevector-uint-ref u8vector-uint-ref)
(defalias bytevector-uint-set! u8vector-uint-set!)
(defalias bytevector-sint-ref u8vector-sint-ref)
(defalias bytevector-sint-set! u8vector-sint-set!)
(defalias bytevector->uint-list u8vector->uint-list)
(defalias bytevector->sint-list u8vector->sint-list)
(defalias uint-list->bytevector uint-list->u8vector)
(defalias sint-list->bytevector sint-list->u8vector)
(defalias bytevector-u16-ref u8vector-u16-ref)
(defalias bytevector-u16-native-ref u8vector-u16-native-ref)
(defalias bytevector-u16-set! u8vector-u16-set!)
(defalias bytevector-u16-native-set! u8vector-u16-native-set!)
(defalias bytevector-s16-ref u8vector-s16-ref)
(defalias bytevector-s16-native-ref u8vector-s16-native-ref)
(defalias bytevector-s16-set! u8vector-s16-set!)
(defalias bytevector-s16-native-set! u8vector-s16-native-set!)
(defalias bytevector-u32-ref u8vector-u32-ref)
(defalias bytevector-u32-native-ref u8vector-u32-native-ref)
(defalias bytevector-u32-set! u8vector-u32-set!)
(defalias bytevector-u32-native-set! u8vector-u32-native-set!)
(defalias bytevector-s32-ref u8vector-s32-ref)
(defalias bytevector-s32-native-ref u8vector-s32-native-ref)
(defalias bytevector-s32-set! u8vector-s32-set!)
(defalias bytevector-s32-native-set! u8vector-s32-native-set!)
(defalias bytevector-u64-ref u8vector-u64-ref)
(defalias bytevector-u64-native-ref u8vector-u64-native-ref)
(defalias bytevector-u64-set! u8vector-u64-set!)
(defalias bytevector-u64-native-set! u8vector-u64-native-set!)
(defalias bytevector-s64-ref u8vector-s64-ref)
(defalias bytevector-s64-native-ref u8vector-s64-native-ref)
(defalias bytevector-s64-set! u8vector-s64-set!)
(defalias bytevector-s64-native-set! u8vector-s64-native-set!)
(defalias bytevector-ieee-single-ref u8vector-float-ref)
(defalias bytevector-ieee-single-set! u8vector-float-set!)
(defalias bytevector-ieee-single-native-ref u8vector-float-native-ref)
(defalias bytevector-ieee-single-native-set! u8vector-float-native-set!)
(defalias bytevector-ieee-double-ref u8vector-double-ref)
(defalias bytevector-ieee-double-set! u8vector-double-set!)
(defalias bytevector-ieee-double-native-ref u8vector-double-native-ref)
(defalias bytevector-ieee-double-native-set! u8vector-double-native-set!)
(defalias bytevector-swap! u8vector-swap!)
(defalias bytevector-reverse! u8vector-reverse!)
(defalias bytevector-reverse u8vector-reverse)
(defalias bytevector->bytestring u8vector->bytestring)
(defalias bytestring->bytevector bytestring->u8vector)
(defalias bytevector->uint u8vector->uint)
(defalias uint->bytevector uint->u8vector)
Low Level Unsafe Operations
The following low level unsafe operations are also exported.
(&u8vector-s8-ref v i) -> s8
(&u8vector-s8-set! v i n) -> unspecified
(&u8vector-uint-ref/be v i size) -> uint
(&u8vector-uint-ref/le v i size) -> uint
(&u8vector-uint-set!/be v i n size) -> unspecified
(&u8vector-uint-set!/le v i n size) -> unspecified
(&u8vector-sint-ref/be v i size) -> sint
(&u8vector-sint-ref/le v i size) -> sint
(&u8vector-sint-set!/be v i n size) -> unspecified
(&u8vector-sint-set!/le v i n size) -> unspecified
(&u8vector-u16-ref/native v i) -> u16
(&u8vector-u16-set!/native v i n) -> unspecified
(&u8vector-s16-ref/native v i) -> s16
(&u8vector-s16-set!/native v i n) -> unspecified
(&u8vector-u32-ref/native v i) -> u32
(&u8vector-u32-set!/native v i n) -> unspecified
(&u8vector-s32-ref/native v i) -> s32
(&u8vector-s32-set!/native v i n) -> unspecified
(&u8vector-u64-ref/native v i) -> u64
(&u8vector-u64-set!/native v i n) -> unspecified
(&u8vector-s64-ref/native v i) -> s64
(&u8vector-s64-set!/native v i n) -> unspecified
(&u8vector-float-ref/native v i) -> flonum
(&u8vector-float-set!/native v i x) -> unspecified
(&u8vector-double-ref/native v i) -> flonum
(&u8vector-double-set!/native v i x) -> unspecified
(&u8vector-swap! v i j) -> unspecified
Asynchronous Completions
To use the bindings from this module:
(import :std/misc/completion)
make-completion
(make-completion) -> completion
(make-completion name) -> completion
Creates a new asynchronous completion, a synchronization construct which blocks
until a thread signals that its task either succeeded or failed via
completion-post!
or completion-error!
, respectively, notifying all waiting
threads about the result.
An optional name
may be provided for debugging purposes:
if you deadlock, you'll be able to more easily identify which completion went wrong.
Examples:
> (import :gerbil/gambit/threads :std/iter :std/sugar)
> (def (task i c)
(displayln i ": waiting")
(try (displayln i ": " (completion-wait! c))
(catch (ex) (displayln i ": " ex))
(finally (displayln i ": exiting"))))
> (let* ((c (make-completion))
(threads (for/collect (i (in-iota 3))
(spawn task i c))))
(spawn-thread
(lambda ()
(displayln "error in computation, notifying all")
(completion-error! c 'did-not-finish)))
(for-each thread-join! threads))
0: waiting
1: waiting
2: waiting
error in computation, notifying all
0: did-not-finish
0: exiting
1: did-not-finish
1: exiting
2: did-not-finish
2: exiting
completion?
(completion? c) -> boolean
c := completion to check
Returns #t
if c is a completion, #f
otherwise.
Examples:
> (completion? (make-completion))
#t
> (import :gerbil/gambit/threads)
> (completion? (make-condition-variable))
#f
completion-ready?
(completion-ready? c) -> boolean
c := completion to check
Returns #t
if the completion is ready, #f
otherwise. This operation is
non-blocking.
Examples:
> (def c (make-completion))
> (completion-ready? c)
#f
> (completion-post! c 'DONE)
> (completion-ready? c)
#t
completion-wait!
(completion-wait! c) -> any | error
c := completion to wait on
Waits on c until it has been posted with completion-post!
or an error has
been signaled with completion-error!
. If the completion was posted, the posted
value is returned. If an error was signalled, then it is raised as an exception.
Examples:
> (def c (make-completion))
> (spawn completion-wait! c)
#<thread #3>
> (spawn completion-wait! c)
#<thread #4>
> (spawn completion-wait! c)
#<thread #5>
> (completion-post! c 'done) ; all waiting threads will be notified
> (map thread-dead? [#3 #4 #5])
(#t #t #t)
> (completion-wait! c) ; already completed, not waiting
done
> (def c (make-completion))
> (spawn completion-error! c 'failure)
#<thread #8>
> (completion-wait! c)
*** ERROR IN (console)@1986.1 -- This object was raised: failure
completion-post!
(completion-post! c val) -> void | error
c := completion to post
val := result value
Signals to c that the current thread's computation is complete. All other waiting threads will be notified and receive val as the result.
Calling completion-post!
on an already completed c will raise an error.
Examples:
> (import :gerbil/gambit/threads :std/iter)
> (def (task i c)
(displayln i ": waiting")
(displayln i ": " (completion-wait! c)))
> (let* ((c (make-completion))
(threads (for/collect (i (in-iota 3))
(spawn task i c))))
(thread-sleep! 1)
(displayln "main: done")
(completion-post! c 'ok)
(for-each thread-join! threads))
0: waiting
1: waiting
2: waiting
main: done
0: ok
1: ok
2: ok
;; Completions are expected to be posted once. The following would
;; be a race condition, either succeeding fine or raising an error:
> (let (c (make-completion))
(spawn completion-post! c 'done) ; silently fails in case of error
(completion-post! c 'done)
(completion-wait! c))
;; either of these:
*** ERROR IN (console)@2019.5 -- Completion has already been posted #<completion #26>
done
completion-error!
(completion-error! c obj) -> void | error
c := completion to notify
obj := exception object to raise
Signals an error to c, with obj being the exception argument that's raised, notifying all waiting threads that a problem occurred.
Calling completion-error!
on an already completed c will raise an error.
Examples:
> (import :gerbil/gambit/threads :std/sugar)
> (let (c (make-completion))
(spawn completion-error! c 'failure)
(try (completion-wait! c) ; failure is raised in all waiting threads
(catch (ex) (display-exception ex (current-error-port)))))
This object was raised: failure
with-completion-error
(with-completion-error c body ...) -> any | error
c := completion to notify when error occurs
body ... := expressions to evaluate
Wraps body ... with an exception handler that notifies c via
completion-error!
if any type of error is raised within the body expressions.
Furthermore, errors will propagate upwards and need to be handled, terminating
the thread otherwise.
Examples:
> (import :gerbil/gambit/threads :std/sugar)
> (def (task c)
(with-completion-error c
(displayln (/ 7 0)) ; call completion-error! and silently terminate thread
(completion-post! c)))
> (let (c (make-completion))
(spawn task c)
(try
(completion-wait! c)
(displayln "All done!")
(catch (ex) (display-exception ex (current-error-port)))))
Divide by zero
(/ 7 0)
completion
(defsyntax completion)
Completion type for user-defined generics and destructuring.
Examples:
> (import :gerbil/gambit/threads)
> (def c (make-completion))
> (completion-post! c 'done)
> (with ((completion mut cond-var ready? val ex) c)
(with-lock mut
(cut displayln "value: " (if ready? val "not ready"))))
value: done
Thread Barriers
To use the bindings from this module:
(import :std/misc/barrier)
make-barrier
(make-barrier n) -> barrier | error
n := thread wait limit, fixnum
Creates a thread barrier, a synchronization construct that blocks up to n threads before it allows them to proceed. n needs to be a non-negative fixnum, otherwise an error is signaled.
Examples:
> (import :gerbil/gambit/threads :std/iter)
> (def (thread-task b)
(thread-sleep! (+ 2 (random-integer 3)))
(displayln (current-thread) " completed its task. Waiting for others.")
(barrier-post! b)
(barrier-wait! b)
(displayln (current-thread) " runs again."))
> (let* ((b (make-barrier 3))
(threads (for/collect (i (in-iota 5))
(spawn thread-task b))))
(for-each thread-join! threads)
(displayln "All threads are done."))
#<thread #1> completed its task. Waiting for others.
#<thread #2> completed its task. Waiting for others.
#<thread #3> completed its task. Waiting for others.
#<thread #1> runs again. ; barrier limit reached, notifying waiting threads
#<thread #2> runs again.
#<thread #3> runs again.
#<thread #4> completed its task. Waiting for others.
#<thread #4> runs again. ; limit already reached, no need to wait
#<thread #5> completed its task. Waiting for others.
#<thread #5> runs again.
All threads are done.
barrier?
(barrier? b) -> boolean
b := barrier to check
Returns #t
if b is a barrier, #f
otherwise.
Examples:
> (barrier? (make-barrier 3))
#t
(import :gerbil/gambit/threads)
> (barrier? (make-mutex 'barrier))
#f
barrier-wait!
(barrier-wait! b) -> void | error
b := barrier to wait on
Waits on b until it has been posted n times (as specified on barrier
creation) with barrier-post!
or an error has been signaled with
barrier-error!
. Errors will propagate upwards and need to be handled.
Examples:
> (import :gerbil/gambit/threads :std/iter :std/format)
> (def (log msg)
(printf "~a@~a: ~a\n" (current-thread) (time->seconds (current-time)) msg))
> (def (task b)
(log "working")
(thread-sleep! (random-integer 5))
(barrier-post! b)
(log "waiting for other threads")
(barrier-wait! b)
(log "done"))
> (let (b (make-barrier 3))
(for-each thread-join! (for/collect (i (in-iota 3)) (spawn task b)))
(barrier-wait! b) ; barrier thread limit already reached, not waiting
(barrier-error! b 'failure)
(barrier-wait! b)) ; unhandled exception, terminates primordial thread
#<thread #1>@1558088315.312991: working
#<thread #2>@1558088315.3137062: working
#<thread #3>@1558088315.3143606: working
#<thread #3>@1558088317.3152056: waiting for other threads
#<thread #1>@1558088318.313868: waiting for other threads
#<thread #3>@1558088319.3144877: done
#<thread #1>@1558088319.314984: done
#<thread #2>@1558088319.3154783: waiting for other threads
#<thread #2>@1558088319.3161075: done
*** ERROR IN (console)@1593.5 -- This object was raised: failure
barrier-post!
(barrier-post! b) -> void
b := barrier to signal to
Signals b that the current thread's computation is complete. All other waiting threads will be notified once b's post limit (as specified on barrier creation) is reached.
Examples:
> (import :gerbil/gambit/threads)
> (let* ((b (make-barrier 2))
(t1 (spawn barrier-post! b))
(t2 (spawn barrier-post! b)))
(barrier-wait! b) ; signaled twice, good to go
'OK)
OK
barrier-error!
(barrier-error! b obj) -> void
b := barrier to signal error on
obj := exception object to raise
Signals an error to b, with obj being the exception argument that's raised, notifying all waiting threads that a problem occurred.
Examples:
> (import :gerbil/gambit/threads :std/sugar)
> (let* ((b (make-barrier 3))
(t (spawn barrier-error! b 'failure)))
(try (barrier-wait! b) ; failure is raised in all waiting threads
(catch (ex) (display-exception ex (current-error-port)))))
This object was raised: failure
with-barrier-error
(with-barrier-error b body ...) -> any | error
b := barrier to notify when error occurs
body ... := expressions to evaluate
Wraps body ... with an exception handler that notifies b via
barrier-error!
if any type of error is raised within the body expressions.
Furthermore, errors will propagate upwards and need to be handled, terminating
the thread otherwise.
Examples:
> (import :gerbil/gambit/threads :std/sugar)
> (def (task b)
(with-barrier-error b
(displayln (/ 7 0)) ; call barrier-error! and silently terminate thread
(barrier-post! b)))
> (let* ((b (make-barrier 2))
(t (spawn task b)))
(try
(barrier-wait! b)
(displayln "All done!")
(catch (ex) (display-exception ex (current-error-port)))))
Divide by zero
(/ 7 0)
barrier
(defsyntax barrier)
Barrier type for user-defined generics and destructuring.
Examples:
> (import :gerbil/gambit/threads)
> (with ((barrier mut cond-var count limit ex) (make-barrier 3))
(with-lock mut
(cut displayln "limit: " count "/" limit)))
limit: 0/3
Deques
To use the bindings from this module:
(import :std/misc/deque)
make-deque
(make-deque) -> deque
Creates a new empty deque, a double-ended queue, which allows adding and removing elements on both ends without walking the whole data structure first.
Examples:
> (import :std/test)
> (let (dq (make-deque))
(check (deque-empty? dq) => #t)
(push-front! dq 10)
(push-front! dq 20)
(push-back! dq 30)
(check (deque-length dq) => 3)
(deque->list dq))
... check (deque-empty? dq) is equal? to #t
... check (deque-length dq) is equal? to 3
(20 10 30)
deque?
(deque? dq) -> boolean
dq := deque to check
Returns #t
if dq is a deque, #f
otherwise.
Examples:
> (let (dq (make-deque))
(push-front! dq (make-deque))
(and (deque? dq)
(deque? (peek-front dq))
(deque? (make-deque))))
#t
> (deque? (list 1 2 3))
#f
deque-length
(deque-length dq) -> fixnum
dq := deque to inspect
Returns the number of elements dq holds.
Similar to retrieving the length of a vector, this operations is O(1)
, since
deques always keep track of their length.
Examples:
> (let (dq (make-deque))
(for-each (cut push-back! dq <>) '(A B C D E F))
(deque-length dq))
6
> (deque-length (make-deque))
0
deque-empty?
(deque-empty? dq) -> boolean
dq := deque to check whether empty
Returns #t
if dq has no elements queued, #f
otherwise.
Examples:
> (deque-empty? (make-deque))
#t
> (let (dq (make-deque))
(push-back! dq (make-deque))
(and (deque-empty? (peek-front dq))
(deque-empty? dq)))
#f
push-front!
(push-front! dq elem) -> unspecified
dq := deque to push onto
elem := element to push to dq
Enqueues (pushes) elem to the front of the dq. Similar to set!
, it's
unspecified what push-front!
returns afterwards.
Examples:
> (let (dq (make-deque))
(push-front! dq 30)
(push-front! dq 20)
(push-front! dq 10)
(deque->list dq))
(10 20 30)
push-back!
(push-back! dq elem) -> unspecified
dq := deque to push onto
elem := element to push to dq
push-back!
is similar to push-front!
, but pushes elem to the back of dq
instead of the front.
Examples:
> (let (dq (make-deque))
(push-back! dq #\a)
(push-back! dq #\b)
(push-back! dq #\c)
(list->string (deque->list dq)))
"abc"
pop-front!
(pop-front! dq [default = absent-obj]) -> any | default | error
dq := deque to pop from
default := optional element returned when dq is empty
Pops the front of dq and returns that value. If dq is empty and a default value is supplied, then default is returned. Otherwise an error is raised.
Examples:
> (let (dq (make-deque))
(for-each (cut push-back! dq <>) [1 2 3])
(displayln (pop-front! dq 0))
(displayln (pop-front! dq 0))
(displayln (pop-front! dq 0))
;; would signal error without default value:
(displayln (pop-front! dq 0)))
1
2
3
0
pop-back!
(pop-back! dq [default = absent-obj]) -> any | default | error
dq := deque to pop from
default := optional element returned when dq is empty
pop-back!
is similar to pop-front!
, but pops the back of dq instead and
returns that value. If dq is empty and a default value is supplied, then
default is returned. Otherwise an error is raised.
Examples:
> (import (group-in :std iter sugar))
> (let (dq (make-deque))
(for ((x "ABCDEF") (y (in-naturals 1)))
(push-front! dq (cons x y)))
(until (deque-empty? dq)
(displayln (pop-back! dq))))
(A . 1)
(B . 2)
(C . 3)
(D . 4)
(E . 5)
(F . 6)
peek-front
(peek-front dq [default = absent-obj]) -> any | default | error
dq := deque to peek front
default := optional element returned when dq is empty
Returns the front value of dq without popping it from the deque like
pop-front!
does. If dq is empty and a default value is supplied, then
default is returned. Otherwise an error is raised.
Examples:
> (let (dq (make-deque))
(push-back! dq 10)
(push-back! dq 20)
(push-back! dq 30)
(peek-front dq))
10
> (import :std/sugar :std/misc/func)
> (let (dq (make-deque))
(for-each (cut push-front! dq <>)
(repeat random-integer 10 10))
(displayln (deque->list dq))
(while (odd? (peek-front dq 0))
(pop-front! dq))
(deque->list dq))
(3 5 5 1 0 4 5 8 6 1)
(0 4 5 8 6 1)
peek-back
(peek-back dq [default = absent-obj]) -> any | default | error
dq := deque to peek back
default := optional element returned when dq is empty
Returns the back value of dq without popping it from the deque like
pop-back!
does. If dq is empty and a default value is supplied, then
default is returned. Otherwise an error is raised.
Examples:
> (let (dq (make-deque))
(push-back! dq 'A)
(push-back! dq 'B)
(push-back! dq 'C)
(displayln (peek-back dq))
(pop-front! dq)
(displayln (peek-back dq))
(pop-back! dq)
(displayln (peek-back dq)))
C
C
B
> (def dq (make-deque))
> (push-back! dq "xyz")
#<deque #15> ; unspecified, don't depend on this
> (peek-back dq 'EMPTY)
"xyz"
> (pop-front! dq)
"xyz"
> (peek-back dq 'EMPTY)
EMPTY
> (peek-back dq)
error
deque->list
(deque->list dq) -> list
dq := deque to transform into list
Returns a new list of the elements queued in dq, in order.
Examples:
> (let (dq (make-deque))
(push-back! dq #\a)
(push-back! dq 100)
(push-back! dq "other")
(deque->list dq))
(#\a 100 "other")
> (import :std/srfi/1)
> (let (dq (make-deque))
(for-each (cut push-front! dq <>) (iota 100))
(drop (deque->list dq) 90))
(9 8 7 6 5 4 3 2 1 0)
deque
(defsyntax deque)
Deque type for user-defined generics and destructuring.
Examples:
> (let (dq (make-deque))
(push-back! dq "first")
(push-back! dq "second")
(push-back! dq "third")
(match dq
((deque front back length)
(displayln "front: " front)
(displayln "back: " back)
(displayln "length: " length))))
front: #<node #17>
back: #<node #18>
length: 3
Hash-table utilities
To use the bindings from this module:
(import :std/misc/hash)
hash-ref/default
(hash-ensure-ref table key default) -> value
Checks whether the given key is present in the table. If it is, return the associated value. If it is not, call the default thunk and return its value.
hash-ensure-ref
(hash-ensure-ref table key default) -> value
Checks whether the given key is present in the table. If it is, return the associated value. If it is not, call the default thunk, associate its return value to the key in the table, and then return the value.
invert-hash
(invert-hash hash to: (to (hash))) -> hash-1
Returns the inverse of a hash table: a hash-table hash-1 whose keys are the values of those of hash, each mapped to one value that is a key that hash associates to the value. If the key is unique, it will be used; if it isn't, any single of those keys may be used.
If an existing hash-table is passed as a to argument, it will be used as hash-table populated with those inverse keys, instead of a new equal-hash-table. This feature can notably be used to provide a hash-table with different equality predicate, or a weak hash-table, or one that is already populated for other reasons.
invert-hash/fold
(invert-hash/fold hash to: (to (hash)) nil: (nil '()) cons: (cons cons)) -> hash-1
Returns the inverse of a hash table: a hash-table hash-1 whose keys are the values of those of hash, each mapped to a value that is fold of all keys that hash associates to the value. By default, the fold consists in consing those keys into a list, in an unspecified order. It could instead create a hash-table of those keys, or a sorted list or tree of them, etc.
If an existing hash-table is passed as a to argument, it will be used as hash-table populated with those inverse keys, instead of a new equal-hash-table. This feature can notably be used to provide a hash-table with different equality predicate, or a weak hash-table, or one that is already populated for other reasons.
invert-hash<-vector
(invert-hash<-vector vector
start: (start 0) end: (end (vector-length from))
to: (to (make-hash-table)) key: (key identity))
-> hash
Returns the inverse of the section of a vector in the given range from start included to end excluded, a hash-table hash whose keys are extracted from values of those of vector by the function key (that defaults to identity, i.e. the value itself is the key), each mapped to one value that is an index that vector associates to the value. If the index is unique, it will be used; if it isn't, any single of those indexes may be used.
If an existing hash-table is passed as a to argument, it will be used as hash-table populated with those inverse keys, instead of a new equal-hash-table. This feature can notably be used to provide a hash-table with different equality predicate, or a weak hash-table, or one that is already populated for other reasons.
invert-hash<-vector/fold
(invert-hash<-vector/fold
from start: (start 0) end: (end (vector-length from))
to: (to (make-hash-table)) nil: (nil '()) cons: (cons cons) key: (key identity)) -> to
Returns the inverse of the section of a vector in the given range from start included to end excluded,
a hash-table hash whose keys are extracted from values of those of vector by the function key
(that defaults to identity, i.e. the value itself is the key),
each mapped to one value that is an index that vector associates to the value.
Each time an index is found for a given key, it is combined together with previous indexes found
using the function cons (which defaults to actual cons
),
with the value nil (which defaults to empty list ()
) being assumed as the initial value
before any index is found.
If an existing hash-table is passed as a to argument, it will be used as hash-table populated
with those inverse indexes, instead of a new equal-hash-table.
This feature can notably be used to provide a hash-table with different equality predicate,
or a weak hash-table, or one that is already populated for other reasons.
hash-restrict-keys
(hash-restrict-keys hash key-list) -> hash-1
Returns a new hash-table hash-1 that has a subset of the keys in hash, associated to the same values as in hash. The key restriction is specified as a list key-list of acceptable keys; if the key in the list is present, the key-value pair is copied to the new table; if it is not present, it is ignored.
hash-value-map
(hash-value-map hash fun) -> hash-1
Return a new hash-table that has the same keys as the original hash, but whose values have been transformed by calling the function fun.
hash-key-value-map
(hash-key-value-map fun from (to (hash)) -> to
Modifies the to hash-table (by default, a new equal?
hash-table),
by calling a function fun on each key value pair of hash-table from
(passed as two arguments key and value), which must return either #f
or a cons pair (k1 . v1)
that will be added to the destination hash-table to.
If a key k1 appears multiple times, it is not guaranteed which will present in the end,
only that one of them will.
Examples:
> (hash->list/sort
(hash-key-value-map
(lambda (k v) (and (even? v) (cons (/ v 2) (string-append k k))))
(hash ("a" 1) ("b" 2) ("c" 3) ("d" 4) ("e" 5) ("f" 6))) <)
((1 . "bb") (2 . "dd") (3 . "ff"))
hash-filter
(hash-filter hash pred (to (hash))) -> hash-1
Return a new hash-table that has a subset of the key-value pairs in the original hash, those for which the predicate pred returns true when called with the key and value as its two arguments.
If the to argument is provided, it is used instead of a new hash-table,
which allows to pre-populate it, use a different equality predicate than equal?
,
or to specify weakness.
hash-remove
(hash-remove hash fun (to (hash))) -> hash-1
Return a new hash-table that has a subset of the key-value pairs in the original hash, those for which the predicate pred returns false when called with the key and value as its two arguments.
If the to argument is provided, it is used instead of a new hash-table,
which allows to pre-populate it, use a different equality predicate than equal?
,
or to specify weakness.
hash-remove-value
(hash-remove-value from val (to (hash))) -> hash-1
Return a new hash-table that has a subset of the key-value pairs in the original hash,
those for which the value is different (not equal?
) to the given argument val.
If the to argument is provided, it is used instead of a new hash-table,
which allows to pre-populate it, to specify weakness, or
to use a different equality predicate than equal?
(beware though that comparison is still with equal?
---
for a different one, use hash-remove
instead with a suitable predicate).
hash-ensure-removed!
(hash-ensure-removed! hash key) -> hash
Remove from the hash any entry with the given key, and return two values:
(a) the value that was removed, if any, or #f
if none was found, and
(b) a boolean that tells if there was a value.
hash-ensure-modify
(hash-ensure-modify! hash key default function) -> value
Modify entry for key in hash. If no entry exists yet, call the provided thunk to compute a default value. Return the new value, after modification.
hash-empty?
(hash-empty? hash) -> bool
Return true if hash is empty.
hash-merge/override
(hash-merge/override hash ...) -> hash
Similar to hash-merge
, creates a new hash-table with the contents of all the arguments provided,
but in case a same key is present in multiple arguments, choose the value in the rightmost argument
(instead of the leftmost as with hash-merge
).
hash-merge/override!
(hash-merge/override! hash hash1 ...) -> hash
Similar to hash-merge!
, modifies the first hash table by updating it with the contents
of all the other arguments provided; however, as with hash-merge/override
,
in case a same key is present in multiple arguments, choose the value in the rightmost argument
(instead of the leftmost as with hash-merge
).
hash->list/sort
(hash->list/sort hash pred) -> list
Similar to hash->list
and table->list
,
this function returns a list of the key-value pairs in the hash table,
but also sorts this list by keys according to the provided predicate pred.
Examples:
> (hash->list/sort (hash (3 "c") (4 "d") (1 "a") (2 "b")) <)
((1 . "a") (2 . "b") (3 . "c") (4 . "d"))
hash-get-set!
(hash-get-set! h k v) -> (void)
hash-get-set! is a synonym for hash-put! that makes it possible to
(set! (hash-get h k) v)
.
Examples:
> (def h (hash))
> (hash-get-set! h 2 "b")
> (hash-get h 2)
"b"
> (set! (hash-get h 2) "B")
> (hash-get h 2)
"B"
hash-ref-set!
(hash-ref-set! h k v) -> (void)
(hash-ref-set! h k d v) -> (void)
hash-ref-set! is a variant of hash-put! that makes it possible to
(set! (hash-ref h k) v)
or (set! (hash-get h k default) v)
.
Examples:
> (def h (hash))
> (hash-ref-set! h "a" 1)
> (hash-get h "a")
1
> (set! (hash-ref h "b") 2)
> (hash-get h "b")
2
> (defrules post-increment! () ((p x) (p x 1)) ((p x y ...) (begin0 x (set! x (+ x y ...)))))
> (post-increment! (hash-ref h "a") 10)
1
> (post-increment! (hash-ref h "a" 0))
11
> (post-increment! (hash-ref h "c" 0))
0
> (post-increment! (hash-ref h "c" 0))
1
List utilities
To use the bindings from this module:
(import :std/misc/list)
length=?, length<? ... length>=?
(length=? lst1 lst2) -> boolean
(length<? lst1 lst2) -> boolean
(length<=? lst1 lst2) -> boolean
(length>? lst1 lst2) -> boolean
(length>=? lst1 lst2) -> boolean
lst1, lst2 := lists to compare
Compares the list lengths of both lst1 and lst2, and returns a truth value
(#t
or #f
).
function | less efficient variant |
---|---|
(length=? x y) | (= (length x) (length y)) |
(length<? x y) | (< (length x) (length y)) |
(length<=? x y) | (<= (length x) (length y)) |
(length>? x y) | (> (length x) (length y)) |
(length>=? x y) | (>= (length x) (length y)) |
These functions are potentially more efficient because they only need to compare the lists up until the point where they start to differ from one another. They will short-circuit once they encounter a difference instead of calculating both lengths up-front.
Also, either of these two lists is allowed to be circular, but not both.
Examples:
> (import :std/srfi/1)
> (def small (iota 10)) ; => (0 1 ... 9)
> (def large (iota 100)) ; => (0 1 ... 99)
> (length=? small large)
#f ; comparison stops as soon as small runs out of elements
> (length<? large (circular-list 1 2 3))
#t ; circular list never runs out of elements
> (length>=? (circular-list 0 1) [])
#t
length=n?, length<n? ... length>=n?
(length=n? lst n) -> boolean | error
(length<n? lst n) -> boolean | error
(length<=n? lst n) -> boolean | error
(length>n? lst n) -> boolean | error
(length>=n? lst n) -> boolean | error
lst := list to compare
n := number
Checks how the length of lst compares to n and returns a truth value result
(#t
or #f
). Signals an error when n isn't a valid number.
function | less efficient variant |
---|---|
(length=n? x n) | (= (length x) n) |
(length<n? x n) | (< (length x) n) |
(length<=n? x n) | (<= (length x) n) |
(length>n? x n) | (> (length x) n) |
(length>=n? x n) | (>= (length x) n) |
These functions are potentially more efficient because they only need to check the list for up to n elements instead of calculating lst's length up-front.
Also, lst is allowed to be circular.
Examples:
> (length=n? [#\a #\b #\c] 3)
#t
> (import :std/srfi/1)
> (length>=n? (circular-list 0 1) 5)
#t
> (length<n? (circular-list 1 2 3) 10000)
#f ; circular list never runs out of elements
call-with-list-builder
(call-with-list-builder proc) -> list
proc := procedure that takes two proc identifiers as input
Takes a procedure or lambda proc which itself takes two procedures that can have any name but are called put! and peek here:
- put! will append its input element onto an internal list (and thus modifies it) on each invocation.
- peek retrieves the elements collected so far, or
[]
if put! is never called.
Finally, call-with-list-builder
returns the constructed list.
Examples:
> (import :std/iter)
> (call-with-list-builder
(lambda (put! peek)
(for (x (in-range 5 10))
(displayln (peek))
(put! (random-integer (1+ x))))))
() ; no prior put!
(5)
(5 6)
(5 6 2)
(5 6 2 8) ; fifth explicit peek call
(5 6 2 8 6) ; peek is called implicitly at the end
with-list-builder
(with-list-builder (put! [peek]) body ...) -> list
put! := function identifier that modifies internal list
peek := optional function identifier that retrieves internal list
Syntax sugar for the call-with-list-builder
procedure, so put! and peek can
be used without wrapping them in a lambda first. with-list-builder
returns the
internal list at the end.
Examples:
> (import :std/iter)
> (with-list-builder (put!)
(for (n (in-iota 100 1))
(let ((mod3 (zero? (modulo n 3)))
(mod5 (zero? (modulo n 5))))
(put! (cond ((and mod3 mod5) "fizzbuzz")
(mod3 "fizz")
(mod5 "buzz")
(else n))))))
(1 2 "fizz" 4 "buzz" "fizz" ... 97 98 "fizz" "buzz")
snoc
(snoc elem lst) -> list | error
elem := element to append to lst
lst := proper list
snoc
is similar to cons
, but appends elem at the end of lst instead of
putting it at the front.
Different from cons
: snoc
will signal an error when lst is not a proper
list. cons
, in contrast, constructs a pair out of these two input values.
Examples:
> (cons 4 [1 2 3])
(4 1 2 3)
> (snoc 4 [1 2 3])
(1 2 3 4)
> (cons 1 2)
(1 . 2)
> (snoc 1 2)
error ; expects a list as second argument
> (snoc '(a b c) '())
((a b c))
append1
(append1 lst elem) -> list | error
lst := proper list
elem := element to append to lst
append1
is similar to append
, but is constructing a proper list whereas the
latter returns an improper list when appending a non-list elem to lst.
append
also joins two or more input lists while append1
simply adds the
second list as-is.
Signals an error when lst is not a list.
Examples:
> (append [1 2 3] 4)
(1 2 3 . 4)
> (append1 [1 2 3] 4)
(1 2 3 4)
> (append [1 2 3] [4 5 6])
(1 2 3 4 5 6)
> (append1 [1 2 3] [4 5 6])
(1 2 3 (4 5 6))
> (append1 "raise" "error")
error ; expects a list as first argument
for-each!
(for-each! lst proc) -> void
lst := proper or even improper list
proc := procedure called for side-effects
for-each!
is similar to for-each
, but the arguments lst and proc are
swapped which allows better nesting. Another slight difference: for-each!
even
accepts improper lists.
Examples:
> (def exprs [[2 + 0] [2 - 0] [0 * 2] [2 / 0] [0 / 2]])
> (for-each (match <>
([x (eq? /) (? zero? y)]
(displayln "div by zero!"))
([x op y]
(displayln (op x y))))
exprs)
> (for-each! exprs
(match <>
([x (eq? /) (? zero? y)]
(displayln "div by zero!"))
([x op y]
(displayln (op x y)))))
;; both print:
2
2
0
div by zero!
0
> (for-each displayln '(1 2 . 3))
error ; list expected
> (for-each! '(1 2 . 3) displayln)
1
2 ; dotted list ending not included
push!
(push! elem lst) -> unspecified | error
elem := element to cons onto lst
lst := list
Macro that conses elem onto lst and set!
s lst accordingly. lst needs
to be bound beforehand or it signals an error. It's unspecified what push!
returns otherwise.
Examples:
> (def lst [])
> (push! 10 lst)
> (push! 20 lst)
> (push! 30 lst)
> lst
(30 20 10)
> (def pair [#\b . #\a])
> (push! #\c pair)
> pair
(#\c #\b . #\a)
> (push! 1 [2 3])
error ; uses set! internally, requires valid binding
pop!
(pop! lst) -> #f | elem
elem := first element from lst
lst := list, from which the first element will be removed
Macro that checks whether the lst is a cons cell, if so returns the car of that cons cell,
and sets lst to the cdr of that cons cell, otherwise returns #f
.
Examples:
> (def lst [])
> (pop! lst)
#f
> (push! 10 lst)
> (push! 20 lst)
> (pop! lst)
20
> (pop! lst)
10
> (pop! lst)
#f
flatten
(flatten lst) -> list
lst := proper nested list-of-lists
Removes all layers of nesting from lst, which is expected to be a proper list-of-lists (or tree structure). It will ignore any empty lists it encounters while traversing, not adding them to the returned flattened list.
Examples:
> (flatten [1 [2 3] [[4 5]]])
(1 2 3 4 5)
> (flatten [1 [] [2 [3 [] 4] 5]])
(1 2 3 4 5) ; ignores empty sublists
> (flatten '((a . 1) (b . 2) (c . 3)))
(a b c) ; expects proper non-dotted list-of-lists
flatten1
(flatten1 lst) -> list | error
lst := proper nested list-of-lists
flatten1
is a special variant of flatten
which will not flatten the whole
nested list-of-lists (or tree structure), but instead removes only a single layer of
nesting from lst.
Note: lst is expected to be a list of proper lists, association lists will signal an error.
Examples:
> (flatten1 [1 [2 3] [[4 5]]])
(1 2 3 (4 5))
> (flatten1 [1 [] [2 [3 [] 4] 5]])
(1 2 (3 () 4) 5)
> (import :std/srfi/1)
> (map (cut iota <>) [1 2 3 4]
((0) (0 1) (0 1 2) (0 1 2 3))
> (flatten (map (cut iota <>) [1 2 3 4]))
(0 0 1 0 1 2 0 1 2 3)
> (flatten1 '((a . 1) (b . 2) (c . 3)))
error ; expects proper non-dotted list-of-lists
unique
unique!
(unique lst [test = equal?]) -> list
lst := proper list
test := test procedure which takes two arguments, defaults to equal?
Alias for delete-duplicates
and delete-duplicates!
(SRFI 1).
Examples:
> (unique [1 2 3 2])
(1 2 3)
> (let (lst [1 2])
(unique [lst lst] ev?))
((1 2))
duplicates
(duplicates lst [test = equal?] [key: #f]) -> list
lst := proper list
test := test procedure which takes two arguments, defaults to equal?
key := optional unary procedure to get the to compare item out of a list element
duplicates
returns a cons cells (item . count)
for every element
that occurs more than once in lst
. If key:
is not #f
the unary procedure is applied to every element before comparison.
Examples:
> (duplicates ['a 1 'a])
((a . 2))
> (duplicates [1 2 3])
()
> (duplicates '((a . 10) (b . 10)) key: cdr)
((10 . 2))
rassoc
(rassoc elem alist [pred = eqv?]) -> pair | #f
elem := element to search for in alist
alist := association list
pred := comparison predicate, optional
rassoc
is similar to assoc
, but instead of comparing elem with the first
element of each pair in alist the optional predicate pred (which defaults to
eqv?
) will compare with the pair's second element.
Returns the first pair in alist whose cdr
satisfies the predicate pred, or #f
otherwise.
Examples:
> (rassoc 2 '((a . 1) (b . 2) (c . 2) (d . 1)))
(b . 2)
> (rassoc "a" '((1 . "a") (2 . "b")))
#f ; eqv? is used by default
> (rassoc "a" '((1 . "a") (2 . "b")) string=?)
(1 . "a")
> (rassoc '(5 6) '((a 1 2) (b 3 4) (c 5 6)) equal?)
(c 5 6) ; equivalent to '(c . (5 6))
when-list-or-empty
(when-list-or-empty lst body ...) -> body ... | []
lst := value or list on which expansion depends
Macro which expands to body expressions only if lst is a non-empty list, otherwise an empty list is returned.
Examples:
> (let (nums [1 2 3])
(when-list-or-empty nums
(cdr nums)))
(2 3)
> (when-list-or-empty []
(cons "never" "expanded"))
()
slice
(slice lst start [limit = #f]) -> list
lst := proper list
start := start index
limit := number of items to take from lst
Returns a list from lst
, starting from the left at start
,
containing limit
elements.
Examples:
> (slice [1 2 3 4] 2)
(3 4)
> (slice [1 2 3 4] 2 1)
(3)
slice-right
(slice-right lst start [limit = #f]) -> list
lst := proper list
start := start index from the right of lst
limit := number of items to take from lst
Returns a list from lst
, starting from the right at start
,
containing limit
elements.
Examples:
> (slice-right [1 2 3 4] 2)
(1 2)
> (slice-right [1 2 3 4] 2 1)
(2)
slice!
(slice! lst start [limit = #f]) -> list
lst := proper list
start := start index
limit := number of items to take from lst
Returns a sublist by potentially updating the input list lst
.
Starting from the left at start
, containing limit
elements.
Examples:
> (def lst [1 2 3 4 5])
> (slice! lst 2 2)
(3 4)
slice-right!
(slice-right! lst start [limit = #f]) -> list
lst := proper list
start := start index from the right of lst
limit := number of items to take from lst
Returns a sublist by potentially updating the input list lst
.
Starting from the right at start
, containing limit
elements.
Examples:
> (def lst [1 2 3 4 5])
> (slice-right! lst 2 2)
(2 3)
butlast
(butlast lst) -> list
lst := proper list
butlast
returns a copy of the proper list lst
, except the last element.
When lst
is empty, lst
is returned as it is.
Examples:
> (butlast [1 2 3])
(1 2)
> (butlast [])
()
split
(split lst proc [limit = #f]) -> list
lst := proper list
stop := value or unary procedure
limit := optional, split the list only limit times
split the list lst
into a list-of-lists using the value or
unary procedure stop
. If limit is set, split the list only limit times.
Examples:
(split '(1 2 "hi" 3 4) string?)
> ((1 2) (3 4))
(split [1 2 0 3 4 0 5 6] 0 1)
> ((1 2) (3 4 0 5 6))
(split [] number?)
> ()
take-until
take-until!
(take-until pred lst) -> list
pred := predicate
lst := proper or circular list
take-until
returns a list with all elements before pred
returns #t
.
take-until!
is the linear-update variant of take-until
.
Examples:
> (take-until number? ['a "hi" 1 'c])
(a "hi")
drop-until
(drop-until pred lst) -> list
pred := predicate
lst := proper or circular list
drop-until
returns a list with all elements from the point on pred
returns #t
.
Examples:
> (drop-until number? ['a [] "hi" 1 'c])
(1 c)
group
(group lst [test = equal?]) -> list
lst := proper list
test := optional, binary predicate
group consecutive elements of the list lst
into a list-of-lists.
Examples:
> (group [1 2 2 3 1 1])
((1) (2 2) (3) (1 1))
> (import :std/sort)
> (group (sort [1 2 2 3 1 1] <) eqv?)
((1 1 1) (2 2) (3))
every-consecutive?
(every-consecutive? pred lst)
returns a boolean that is true if any two consecutive terms in the list satisfy the predicate. In particular, if the predicate is a partial order predicate (respectively a strict partial order predicate), then the list is totally ordered (respectively strictly totally ordered) according to the predicate.
Examples:
> (every-consecutive? <= [1 2 2 3 10 100])
#t
> (every-consecutive? < [5 1 8 9])
#f
first-and-only
(first-and-only lst)
returns the first and only value of a singleton list, or raises an error if the argument wasn't a singleton list.
Examples:
> (first-and-only '(42))
42
> (first-and-only '())
*** ERROR IN std/misc/list#first-and-only, -515899390@648.11 -- Assertion failed (and (pair? x) (null? (cdr x)))
A-List utilities
Utilities to manipulate alists, i.e. association lists, i.e. of key-value pairs.
To use the bindings from this module:
(import :std/misc/alist)
alist?
(alist? alist) -> boolean
alist := association list to check
Checks whether alist is a proper association list and returns a truth value
(#t
or #f
). alist needs to be finite, circular lists are not supported.
A proper association list is a list of pairs and may be of the following forms:
((key1 . value1) ...)
((key1 value1) ...)
Examples:
> (alist? '((a . 1) (b . 2) (c . 3)))
#t
> (alist? [["one" #\1] ["two" #\2] ["three" #\3]])
#t
> (alist? '((a . 1) ("two" #\2) (1 2 3 4)))
#t ; (1 2 3 4) is equivalent to (1 . (2 3 4))
> (alist? '(a 1 b 2 c 3))
#f ; input is a plist, see plist? function
> (alist? '())
#t ; edge-case, just like (list? '()) => #t
plist->alist
(plist->alist plist) -> alist | error
plist := property list to transform
Transforms a property list (k1 v1 k2 v2 ...)
into an association list ((k1 . v1) (k2 . v2)...)
. plist needs to be finite, circular lists are not supported.
Furthermore, an error is signaled when plist is a improper property list.
Examples:
> (plist->alist [10 "cat" 11 "dog" 12 "bird"])
((10 . "cat") (11 . "dog") (12 . "bird"))
> (plist->alist ["semicolon" #\; "comma" #\, "dot"])
error ; key "dot" has no associated property value
> (plist->alist [])
()
assq-set!, assv-set!, assoc-set!
(asetq! alist key value)
(assq-set! key alist value)
(asetv! alist key value)
(assv-set! key alist value)
(aset! alist key value)
(assoc-set! key alist value)
These functions kind-of complement the assq
, assv
, assoc
functions from the prelude,
and enable the destructive update of an alist (association list), i.e. a association list
that follows the [[key1 . value1] [key2 . value2] ... [keyN . valueN]]
,
by either modifying in-place an entry with the given key, or adding a new entry.
The functions all return #!void
.
Just like the alist getter functions, these functions are distinguished by which
equality predicate is used to compare keywords: eq?
, eqv?
or equal?
respectively.
eq?
is best for keys being symbols and keywords, eqv?
for numbers, and equal?
for strings
or lists, etc.
Each function comes in two variant:
the first one accepts (asetq! alist key value)
as the order of arguments,
whereas the second one follows the convention so that you can use (set! (assq key alist) value)
or (set! (assv key alist) value)
.
Note that in the last case, assq
, assv
and assoc
return a pair,
whereas the setter functions take just the value as argument.
That's why we say these setter functions only "kind-of" complement the respective getter functions.
Last but not least, destructive operations are not allowed on an empty alist.
If you use aset!
or its friends, you have to ensure your alists are never empty.
For instance you may keep a dummy key at the end of your alist that never gets removed.
If this constraint is not acceptable, you may instead storing your alist
in a variable (or struct field), use the pure aset
operation, and update
the variable (or struct field) with the result of it.
Examples:
(let (p [['a . 1]['b . 2]]) (asetq! p 'a 3) p)
> [['a . 3]['b . 2]]
(let (p [['a . 1]['b . 2]]) (asetq! p 'c 3) p)
> [['c . 3]['a . 1]['b . 4]]
aremq!, aremv!, arem!
(aremq! key plist)
(aremv! key plist)
(arem! key plist)
These functions destructively modify an alist (association-list) to remove the entry for a given key.
Just like the alist getter and setter functions, these functions are distinguished by which
equality predicate is used to compare keywords: eq?
, eqv?
or equal?
respectively.
See aset!
above about alists. The functions all return #!void
.
It is not allowed to destructively remove the last entry in an alist.
If you use arem!
or its friends, you have to ensure your alists are never empty.
For instance you may keep a dummy key at the end of your alist that never gets removed.
If this constraint is not acceptable, you may instead storing your alist
in a variable (or struct field), use the pure arem
operation, and update
the variable (or struct field) with the result of it.
Examples:
(let (p [['a . 1]['b . 2]]) (aremq! 'a p) p)
> [['b . 2]]
(let (p [['a . 1]['b . 2]]) (arem! 'c p) p)
> [['a . 1]['b . 2]]
acons
(acons k v alist) -> alst
k := key
v := value
alist := association list
alst := new association list with additional binding
Adds a new key-value pair to an existing alist in a pure way, a bit like aset
.
Unlike aset
and its friends, however, acons
does not try to replace older bindings
for the same key, and will instead shadow them.
This can cause performance issues if a same key is used a lot of times with acons
,
with the list growing ever longer; this can cause even more interesting correctness issues
if arem
is used subsequently used, which will remove only the most recent binding,
revealing the previous one again, which may or may not be the desired behavior.
Thus, we recommend only using acons
when you otherwise know k
is not yet bound in the alist.
acons
is analogous to the same-named function from Common Lisp, and
(acons k v l)
is synonymous with (cons (cons k v) l)
.
Examples:
> (acons 1 "a" '((2 . "b") (3 . "c")))
((1 . "a") (2 . "b") (3 . "c"))
> (acons 1 "a" '((1 . "b")))
((1 . "a") (1 . "b"))
P-List utilities
Utilities to manipulate plists, i.e. property lists, i.e. even-length lists where each even-indexed element is a key, and each following odd-indexed element is the associated value.
To use the bindings from this module:
(import :std/misc/plist)
plist?
(plist? plist) -> boolean
plist := property list to check
Checks whether plist is a proper property list and returns a truth value (#t
or #f
). plist needs to be finite, circular lists are not supported.
A proper property list is a list of alternating keys and values of the following
form: ((key1 value1 key2 value2 ...))
Examples:
> (plist? '(a 1 b 2 c 3 d 4))
#t
> (plist? ["uno" [1 2 3] "dos" [4 5 6] "tres" [7 8 9]])
#t
> (plist? '((a . 1) (b . 2)))
#t ; key1 = (a . 1), value1 = (b . 2)
> (plist? '((a . 1) (b . 2) (c . 3)))
#f ; key2 = (c . 3), but missing value2
> (plist? [])
#t ; edge-case, just like (list? []) => #t
alist->plist
(alist->plist alist) -> plist | error
alist := association list to transform
Transforms an association list ((k1 . v1) (k2 . v2) ...)
into a property list
(k1 v1 k2 v2 ...)
. alist needs to be finite, circular lists are not supported.
Furthermore, an error is signaled when alist is an improper association list.
Examples:
> (alist->plist [[1 . 10] [2 . 20] [3 . 30]])
(1 10 2 20 3 30)
> (alist->plist [["fire" #\f] ["water" #\w] ["earth" #\e]])
("fire" (#\f) "water" (#\w) "earth" (#\e))
> (alist->plist '((1 2 3) (4 5 6) (7 8 9)))
(1 (2 3) 4 (5 6) 7 (8 9))
> (alist->plist '((a) (b) (c)))
(a () b () c ())
> (alist->plist [])
()
psetq!, psetv!, pset!, pgetq-set!, pgetv-set!, pget-set!
(psetq! plist key value)
(pgetq-set! key plist [default] value)
(psetv! plist key value)
(psetv-set! key plist [default] value)
(pset! plist key value)
(pset-set! key plist [default] value)
These functions complement the pgetq
, pgetv
, pget
functions from the prelude,
and enable the destructive update of a plist (property-list), i.e. a association list
that follows the [key1 value1 key2 value2 ... keyN valueN]
,
by either modifying in-place an entry with the given key, or adding a new entry.
The functions all return #!void
.
Just like the plist getter functions, these functions are distinguished by which
equality predicate is used to compare keywords: eq?
, eqv?
or equal?
respectively.
eq?
is best for keys being symbols and keywords, eqv?
for numbers, and equal?
for strings
or lists, etc.
Each function comes in two variant:
the first one accepts (psetq! plist key value)
as the order of arguments,
whereas the second one follows the convention so that you can use (set! (pgetq key plist) value)
or (set! (pgetq key plist default) value)
.
In the last case, the default value is ignored, but accepted for compatibility with
macros that use both a getter then a setter for a same expression.
Last but not least, destructive operations are not allowed on an empty plist.
If you use pset!
or its friends, you have to ensure your plists are never empty.
For instance you may keep a dummy key at the end of your plist that never gets removed.
If this constraint is not acceptable, you may instead storing your plist
in a variable (or struct field), use the pure pset
operation, and update
the variable (or struct field) with the result of it.
Examples:
(let (p ['a 1 'b 2]) (psetq! p 'a 3) p)
> ['a 3 'b 2]
(let (p ['a 1 'b 2]) (psetq! p 'c 3) p)
> ['c 3 'a 1 'b 4]
premq!, premv!, prem!
(premq! key plist)
(premv! key plist)
(prem! key plist)
These functions destructively modify a plist (property-list) to remove the entry for a given key.
Just like the plist getter and setter functions, these functions are distinguished by which
equality predicate is used to compare keywords: eq?
, eqv?
or equal?
respectively.
See pset!
above about plists. The functions all return #!void
.
It is not allowed to destructively remove the last entry in a plist.
If you use prem!
or its friends, you have to ensure your plists are never empty.
For instance you may keep a dummy key at the end of your plist that never gets removed.
If this constraint is not acceptable, you may instead storing your plist
in a variable (or struct field), use the pure prem
operation, and update
the variable (or struct field) with the result of it.
Examples:
(let (p ['a 1 'b 2]) (premq! 'a p) p)
> ['b 2]
(let (p ['a 1 'b 2]) (prem! 'c p) p)
> ['a 1 'b 2]
LRU caches
To use the bindings from this module:
(import :std/misc/lru)
make-lru-cache
(make-lru-cache cap) -> lru-cache | error
cap := max capacity of cache, fixnum
Creates a new empty Least Recently Used (LRU) cache, a cache replacement data structure, which tracks element usage and drops seldom used ones when full to make room for new elements. cap is the capacity, i.e., the number of elements the cache can hold before purging older entries. cap needs to be at least 1, otherwise an error is signaled.
Examples:
> (def lru (make-lru-cache 3))
> (lru-cache-put! lru 'a "heavy-load-01")
> (lru-cache-put! lru 'b "heavy-load-02")
> (lru-cache-put! lru 'c "heavy-load-03")
> (lru-cache-put! lru 'd "heavy-load-04")
> (lru-cache-put! lru 'e "heavy-load-05")
> (lru-cache-size lru)
3 ; cache full, older entries are eviced first:
> (lru-cache->list lru)
((e . "heavy-load-05") (d . "heavy-load-04") (c . "heavy-load-03"))
> (lru-cache-get lru 'c)
"heavy-load-03" ; cache hit, bring to front:
> (lru-cache->list lru)
((c . "heavy-load-03") (e . "heavy-load-05") (d . "heavy-load-04"))
> (import :std/iter)
> (for ((values key load) lru)
(displayln "key: " key ", load: " load))
key: c, load: heavy-load-03
key: e, load: heavy-load-05
key: d, load: heavy-load-04
lru-cache?
(lru-cache? lru) -> boolean
lru := lru-cache to check
Returns #t
if lru is an LRU cache, #f
otherwise.
Examples:
> (lru-cache? (make-lru-cache 25))
#t
lru-cache-size
(lru-cache-size lru) -> fixnum
lru := lru-cache to check
Returns the current size (i.e., the number of elements the LRU cache holds, not the capacity) of lru.
Examples:
> (let (lru (make-lru-cache 3))
(lru-cache-put! lru 1 #x01)
(lru-cache-put! lru 2 #x02)
(lru-cache-size lru))
2
> (lru-cache-size (make-lru-cache 1000))
0
lru-cache-capacity
(lru-cache-capacity lru) -> fixnum
lru := lru-cache to check
Returns the maximum number of elements lru can hold.
Examples:
> (let (lru (make-lru-cache 3))
(lru-cache-put! lru 1 #x01)
(lru-cache-put! lru 2 #x02)
(lru-cache-capacity lru))
3
> (lru-cache-capacity (make-lru-cache 1000))
1000
lru-cache-put!
(lru-cache-put! lru key val) -> void
lru := lru-cache
key, val := key-value association to insert into lru
Puts an association of key to val into lru, in the queue head position to be precise. If the cache is full, then the tail of the LRU queue (i.e., the value least recently used) is dropped from the cache.
Examples:
> (defstruct resource (raw-data))
> (make-lru-cache 3)
#<lru-cache #35>
> (lru-cache-put! #35 1 (make-resource 'HUGE))
> (lru-cache-put! #35 2 (make-resource 'SMALL))
> (lru-cache-put! #35 3 (make-resource 'LARGE))
> (lru-cache->list #35)
((3 . #<resource #36>) (2 . #<resource #37>) (1 . #<resource #38>))
> (lru-cache-put! #35 4 'non-resource) ; cache is full, evict old element
> (lru-cache->list #35)
((4 . non-resource) (3 . #<resource #36>) (2 . #<resource #37>))
> (resource-raw-data #38)
HUGE
lru-cache-remove!
(lru-cache-remove! lru key) -> void
lru := lru-cache to remove from
key := key to look up
Removes the association of key from lru, making room for a new element in the lru cache.
Examples:
> (make-lru-cache 3)
#<lru-cache #39>
> (lru-cache-put! #39 1 "this")
> (lru-cache-put! #39 1 "that") ; same key, simply updated
> (lru-cache->list #39)
((1 . "that"))
> (lru-cache-remove! #39 1)
> (lru-cache->list #39)
()
lru-cache-ref
(lru-cache-ref lru key [default = absent-obj]) -> any | default | error
lru := lru-cache to access
key := key to look up
default := optional element returned when key can't be found
Returns the association of key in lru, and promotes the node to the head of the LRU queue. If there is no association, then default is returned. If the default is omitted, then an error is raised.
Examples:
> (let (lru (make-lru-cache 3))
(lru-cache-put! lru 'a "file-a")
(lru-cache-put! lru 'b "file-b")
(lru-cache-put! lru 'c "file-c")
(displayln (lru-cache->list lru))
(displayln (lru-cache-ref lru 'b 'NONE))
(displayln (lru-cache->list lru))
(lru-cache-ref lru 'd 'NONE))
((c . file-c) (b . file-b) (a . file-a))
file-b
((b . file-b) (c . file-c) (a . file-a))
NONE
lru-cache-get
(lru-cache-ref lru key) -> any | #f
lru := lru-cache to access
key := key to look up
Same as (lru-cache-ref lru key #f)
.
Examples:
> (lru-cache-get (make-lru-cache 3) 'not-in-here)
#f
lru-cache-flush!
(lru-cache-flush! lru) -> lru-cache
lru := lru-cache to clear
Removes all elements from lru and returns the empty LRU cache. The capacity remains unchanged.
Examples:
> (let (lru (make-lru-cache 100))
(lru-cache-put! lru "first" '1st)
(lru-cache-put! lru "second" '2nd)
(lru-cache-put! lru "third" '3rd)
(displayln (lru-cache-size lru) " " (lru-cache-capacity lru))
(displayln (lru-cache-flush! lru))
(displayln (lru-cache-size lru) " " (lru-cache-capacity lru)))
3 100
#<lru-cache #12>
0 100
lru-cache-for-each
(lru-cache-for-each proc lru) -> void
proc := procedure to be called for each key-value pair in lru
lru := lru-cache
Applies (proc key val)
for every key-value association in lru, in Most
Recently Used (MRU) order. Isn't returning anything, executed for its side
effects.
Examples:
> (make-lru-cache 3)
#<lru-cache #43>
> (for-each (lambda (x) (lru-cache-put! #43 x (* x x))) [1 2 3 4 5])
> (lru-cache-for-each (lambda (k v) (displayln k " " v)) #43)
5 25
4 16
3 9
lru-cache-walk
(lru-cache-walk proc lru) -> void
proc := procedure to be called for each key-value pair in lru
lru := lru cache
Same as (lru-cache-for-each proc lru)
.
lru-cache-fold
(lru-cache-fold proc init lru) -> any
proc := procedure to be called for each key-value pair in lru
init := initial value
lru := lru-cache
Folds lru in Most Recently Used (MRU) order.
proc's signature is expected to look like this: (proc key val prev-intermediate) -> next-intermediate)
. lru-cache-fold
returns the last
next-intermediate value produced by proc. Furthermore, prev-intermediate
will be set to init at the beginning.
Examples:
> (let (lru (make-lru-cache 3))
(lru-cache-put! lru 'a "creatures")
(lru-cache-put! lru 'b "fluffy")
(lru-cache-put! lru 'c "are")
(lru-cache-fold (lambda (k v i) (string-append i " " v))
"gerbils" lru))
"gerbils are fluffy creatures"
lru-cache-foldr
(lru-cache-foldr proc init lru) -> any
proc := procedure to be called for each key-value pair in lru
init := initial value
lru := lru-cache
Where lru-cache-fold
folds in Most Recently Used (MRU) order,
lru-cache-foldr
folds lru in Least Recently Used (LRU) order.
Examples:
> (let (lru (make-lru-cache 5))
(lru-cache-put! lru 'a (iota 5))
(lru-cache-put! lru 'b (iota 4))
(lru-cache-put! lru 'c (iota 3))
(lru-cache-fold (lambda (k v i) (cons v i)) [] lru))
((0 1 2 3 4) (0 1 2 3) (0 1 2))
lru-cache->list
(lru-cache->list lru) -> alist
lru := lru-cache
Returns an alist of key-value associations in lru, in Most Recently Used (MRU) order.
Examples:
> (import :std/srfi/14)
> (make-lru-cache 10)
#<lru-cache #47>
> (for-each (cut lru-cache-put! #47 <> <>) (iota 10) (char-set->list char-set:letter))
> (lru-cache->list #47)
((9 . #\J)
(8 . #\I)
(7 . #\H)
(6 . #\G)
(5 . #\F)
(4 . #\E)
(3 . #\D)
(2 . #\C)
(1 . #\B)
(0 . #\A))
in-lru-cache
(in-lru-cache lru) -> iterator
lru := lru-cache to iterate over
Creates an iterator over lru, yielding key-value values in Most Recently Used (MRU) order.
Examples:
> (import :std/iter)
> (let (lru (make-lru-cache 3))
(lru-cache-put! lru 1 #\a)
(lru-cache-put! lru 2 #\b)
(lru-cache-put! lru 3 #\c)
(for ((values k v) (in-lru-cache lru)) ; or short: (for ((values k v) lru) ...)
(displayln k " -> " v)))
3 -> c
2 -> b
1 -> a
in-lru-cache-keys
(in-lru-cache-keys lru) -> iterator
lru := lru-cache to iterate over
Creates an iterator over lru, yielding keys in Most Recently Used (MRU) order.
Examples:
> (import :std/iter)
> (let (lru (make-lru-cache 3))
(lru-cache-put! lru 1 #\a)
(lru-cache-put! lru 2 #\b)
(lru-cache-put! lru 3 #\c)
(for (x (in-lru-cache-keys lru))
(displayln x)))
3
2
1
in-lru-cache-values
(in-lru-cache-values lru) -> iterator
lru := lru-cache to iterate over
Creates an iterator over lru, yielding values in Most Recently Used (MRU) order.
Examples:
> (import :std/iter)
> (let (lru (make-lru-cache 3))
(lru-cache-put! lru 1 #\a)
(lru-cache-put! lru 2 #\b)
(lru-cache-put! lru 3 #\c)
(for (x (in-lru-cache-values lru))
(displayln x)))
c
b
a
lru-cache
(defsyntax lru-cache)
LRU cache type for user-defined generics and destructuring.
Examples:
;; possible lru-cache iterator implementation:
(defmethod (:iter (lru lru-cache))
(in-lru-cache lru))
(def (in-lru-cache lru)
(def (iterate)
(lru-cache-for-each yield lru))
(in-coroutine iterate))
Port utilities
To use the bindings from this module:
(import :std/misc/ports)
copy-port
(copy-port in out) -> void | error
in := input port to read from
out := output port to write to
Copy all data from port in to port out. Signals an error when in and out
aren't satisfying input-port?
and output-port?
, respectively.
Examples:
> (def nums (string-join (map number->string [1 2 3 4 5]) "\n" 'suffix))
> (call-with-output-file "~/testing/nums.txt"
(lambda (out)
(call-with-input-string nums
(lambda (in) (copy-port in out)))))
$ cat ~/testing/nums.txt ; unix-like command-line
1
2
3
4
5
> (copy-port (current-input-port) (current-output-port))
hello,
hello, ; duplicates what you type at the REPL
everyone!
everyone! ; quit with Ctrl-D
read-all-as-string
(read-all-as-string in) -> string | error
in := input port to read from
Reads all the contents of port in, returning a single string including all newline characters. Signals an error when in can't be read.
Examples:
> (import :std/srfi/13)
> (with-input-from-file "~/dev/gerbil/CHANGELOG.md"
(lambda ()
(string-take (read-all-as-string (current-input-port)) 80)))
"### 2-9-2019: Gerbil-v0.15.1\n\nPatch release to support Gambit v4.9.3\n\nDetails:\n-"
read-all-as-lines
(read-all-as-lines in [separator: #\newline] [include-separator?: #f]) -> list | error
in := input port to read from
separator := character to consider line ending
include-separator? := truth value, whether to include separator char in results
Reads all the contents of port in as a list of strings. The optional separator related keyword parameters specify what is considered a line ending and whether to include these separator characters in the line strings. Signals an error when in can't be read.
Examples:
> (import :std/srfi/1)
> (take (call-with-input-file "~/dev/gerbil/README.md" read-all-as-lines) 4)
("# Gerbil Scheme"
""
"Gerbil is an opinionated dialect of Scheme designed for Systems Programming,"
"with a state of the art macro and module system on top of the Gambit runtime.")
> (with-input-from-string "aa:bb:cc:dd::ff"
(lambda () (read-all-as-lines (current-input-port) separator: #\:)))
("aa" "bb" "cc" "dd" "" "ff")
read-file-string
(read-file-string path) -> string | error
path := path to file to read contents from
Reads contents of the file at path, returning a single string including all newline characters. Signals an error when path can't be read.
Note: There is another optional settings keyword parameter not shown above,
but it's not terribly interesting for this file reading procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more.
Examples:
$ cat ~/testing/nums.txt ; unix-like command-line
1
2
3
4
5
(map string->number
(string-split (read-file-string "~/testing/nums.txt") #\newline))
(1 2 3 4 5)
read-file-lines
(read-file-lines path) -> list | error
path := path to file to read contents from
Reads all lines of the file at path as a list of strings. Signals an error when path can't be read.
Note: There is another optional settings keyword parameter not shown above,
but it's not terribly interesting for this file reading procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more.
Examples:
$ cat ~/testing/nums.txt ; unix-like command-line
1
2
3
4
5
> (read-file-lines "~/testing/nums.txt")
("1" "2" "3" "4" "5")
;; Advent of code 2018, problem 01a: Sum a file of around 1000 exact integer values.
$ head -n5 ~/dev/aoc18/01/input.txt
+12
-13
+17
+17
-10
> (apply + (map string->number (read-file-lines "~/dev/aoc18/01/input.txt")))
508
read-all-as-u8vector
(read-all-as-u8vector in (bufsize 8192)) -> u8vector | error
in := input port to read from
bufsize := buffer size, defaults to 8192 bytes
Reads all the contents of port in, returning a single u8vector. Signals an error when in can't be read.
Examples:
> (def u8 (call-with-input-file "/path/to/file" read-all-as-u8vector))
> (u8vector-length u8)
98526
read-file-u8vector
(read-file-u8vector path settings: [] bufsize: 8192) -> u8vector | error
path := path to file to read contents from
settings := port settings, defaults to the empty list
bufsize := buffer size, defaults to 8192 bytes
Reads contents of the file at path, returning a single u8vector. Signals an error when path can't be read.
Check section 17.7.1 Filesystem devices
of the Gambit Manual if you want
to know more about the settings parameter.
Examples:
> (def u8 (read-file-u8vector "/path/to/file" bufsize: 1024))
> (u8vector-length u8)
98526
write-file-string
(write-file-string file string settings: [] newline-ending: #t) -> void | error
file := the file to be written to
string := the string to write
settings := Gambit path-settings (default [])
newline-ending := append newline if last character is not a newline (default #t)
Write string to file using the display
procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more about the settings
parameter.
Examples:
;; write "Hello, world!\n" to /tmp/foo.txt (may overwrite an existing file)
(write-file-string "/tmp/foo.txt" "Hello, world!") ; \n is appended automatically
;; by using a path-setting we can append a string to an existing file
(write-file-string "/tmp/foo.txt" "hi" settings: [append: #t])
;; the file content is now: "Hello, world!\nhi\n"
;; let's append another string without auto-enforcement of a newline ending
(write-file-string "/tmp/foo.txt" "ho" settings: [append: #t] newline-ending: #f)
;; the final file content is: "Hello, world!\nhi\nho" ; no trail newline character
write-file-lines
(write-file-lines file list settings: [] newline-ending: #t) -> void | error
file := the file to be written to
list := list of strings to write
settings := Gambit path-settings (default [])
newline-ending := append newline if last character is not a newline (default #t)
Write every entry of the list as newline separated line to file using
the display
procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more about the settings
parameter.
Examples:
(write-file-lines "/tmp/foo.txt" ["foo" "bar"])
$ cat /tmp/foo.txt ; unix-like command-line
foo
bar
Port Destructor
(defmethod {destroy <port>} close-port)
The module also defines a destroy
method for ports, so that they can be used
in with-destroy
forms and other primitives that use the destroy idiom,
ensuring that ports will be closed even if an error is signaled somewhere within
the body.
Examples:
> (define (for-each-dir-entry dir proc)
(let ((dir-port (open-directory dir)))
(let loop ()
(let ((file (read dir-port)))
(if (eof-object? file)
(close-port dir-port)
(begin
(proc file)
(loop)))))))
;; could also be written like this utilizing with-destroy:
> (import :std/sugar)
> (define (for-each-dir-entry dir proc)
(let ((dir-port (open-directory dir)))
(with-destroy dir-port
;; dir-port will be closed upon exiting this scope
(let loop ((file (read dir-port)))
(unless (eof-object? file)
(proc file)
(loop (read dir-port)))))))
> (for-each-dir-entry "/home/username" displayln)
dev
downloads
videos
documents
desktop
pictures
music
testing
Priority Queues
To use the bindings from this module:
(import :std/misc/pqueue)
make-pqueue
(make-pqueue prio [cmp = <] [capacity = 15]) -> pqueue
prio := function returning priority of elements
cmp := optional heap comparison function, min-heap by default
capacity := optional initial size
Creates a new empty priority queue, a data structure similar to a simple queue, but extended to take the associated priority value of an element into account when pushing, popping and peeking elements.
- prio is a function returning the real priority of an element. It is utilized to determine the insert position whenever some element is about to be pushed onto the priority queue.
- cmp, a comparison function, is used to order the elements based on their
priority, defaulting to
<
. - capacity is the number of elements the pqueue can hold before it needs to resize itself.
Examples:
For example, let's assume we always want to retrieve the longest string
currently within our queue. This could be achieved with string-length
as our
priority function and ordering these lengths via >
:
> (def pq (make-pqueue string-length >))
> (pqueue-push! pq "shortest")
> (pqueue-push! pq "very, very, long")
> (pqueue-push! pq "medium length")
> (pqueue-peek pq)
"very, very, long"
> (pqueue-pop! pq)
"very, very, long"
> (pqueue-peek pq)
"medium length"
pqueue?
(pqueue? pq) -> boolean
pq := priority queue to check
Returns #t
if pq is a priority queue, #f
otherwise.
Examples:
> (let (pq (make-pqueue identity))
(when (pqueue? pq)
(pqueue-push! pq 1000)
(pqueue-push! pq 100)
(pqueue-push! pq 10)
(pqueue-peek pq)))
10
> (import :std/misc/queue)
> (pqueue? (make-queue))
#f
pqueue-size
(pqueue-size pq) -> fixnum
pq := priority queue to inspect
Returns the number of elements queued in pq.
This operation is O(1)
, since priority queues always keep track of their size.
Examples:
> (let (pq (make-pqueue char->integer))
(string-for-each (cut pqueue-push! pq <>) "ABCDEF")
(pqueue-size pq))
6
pqueue-empty?
(pqueue-empty? pq) -> boolean
pq := priority queue to check
Returns #t
if pq has no elements queued, #f
otherwise.
Examples:
> (pqueue-empty? (make-pqueue identity < 1000))
#t
> (make-pqueue identity)
#<pqueue #21>
> (pqueue-push! #21 1)
> (pqueue-push! #21 3)
> (pqueue-push! #21 5)
> (pqueue-empty? #21)
#f
pqueue-push!
(pqueue-push! pq elem) -> unspecified
pq := priority queue to push onto
elem := element to push to pq
Enqueues elem in pq. The insert position depends on the priority and
comparison functions specified in make-pqueue
. Similar to set!
, it's
unspecified what pqueue-push!
returns afterwards.
Examples:
> (defstruct city (name population))
> (def cities [(make-city "Salto" 104028)
(make-city "Санкт-Петербург" 4879566)
(make-city "Qaqortoq" 3089)
(make-city "አዲስ ፡ አበባ" 3352000)
(make-city "Jayapura" 315872)
(make-city "香港" 7448900)])
> (let (pq (make-pqueue city-population))
(for-each (cut pqueue-push! pq <>) cities)
(city-name (pqueue-peek pq)))
"Qaqortoq"
pqueue-pop!
(pqueue-pop! pq [default = absent-obj]) -> any | default | error
pq := priority queue to pop from
default := optional element returned when pq is empty
Pops the next value in pq and returns it. Which element gets popped depends on
the priority and comparison function specified in make-pqueue
. If pq is
empty and a default value is supplied, then default is returned. Otherwise an
error is raised.
Examples:
> (import :std/sugar)
> (let (pq (make-pqueue values))
(for-each (cut pqueue-push! pq <>) [10 1 20 2 30 3])
(until (pqueue-empty? pq)
(displayln (pqueue-pop! pq)))
;; would signal error without default value:
(pqueue-pop! pq 100))
1
2
3
10
20
30
100
pqueue-peek
(pqueue-peek pq) -> any | error
pq := priority queue to peek
Returns the next value in pq without popping it from the priority queue like
pqueue-pop!
does. An error is raised when pq is empty.
Examples:
> (import :std/srfi/1)
> (def pq (make-pqueue length))
> (pqueue-push! pq (iota (random-integer 10)))
> (pqueue-peek pq)
(0 1 2 3 4 5 6 7)
> (pqueue-push! pq [13 21 34 55])
> (pqueue-peek pq)
(13 21 34 55)
pqueue-contents
(pqueue-contents pq) -> list
pq := priority queue of which to extract the contents
Returns the contents of the priority queue as a list, without modifying the priority queue.
Examples:
> (def pq (make-pqueue identity))
> (for-each (cut pqueue-push! pq <>) [8 2 3 4 1 9 7])
> (pqueue-contents pq)
(1 2 3 8 4 9 7)
pqueue
(defsyntax pqueue)
Priority queue type for user-defined generics and destructuring.
Examples:
> (let (pq (make-pqueue string-length >))
(pqueue-push! pq "Mimas")
(pqueue-push! pq "Enceladus")
(pqueue-push! pq "Tethys")
(with ((pqueue internal-heap) pq)
(with ((vector size . vals) internal-heap)
(displayln "size: " size "\nvals: " vals))))
size: 3
vals: ((9 . Enceladus) (5 . Mimas) (6 . Tethys) 0 0 0 0 0 0 0 0 0 0 0 0)
Process Utilities
To use the bindings from this module:
(import :std/misc/process)
run-process
(run-process cmd
[coprocess: read-all-as-string]
[check-status: #t]
[environment: #f]
[directory: #f]
[stdin-redirection: #t]
[stdout-redirection: #t]
[stderr-redirection: #f]
[pseudo-terminal: #f]
[show-console: #f]) -> any | error
cmd := list of strings, [path . arguments]
coprocess := procedure interacting with process
check-status := procedure or truth value
environment := list of strings, ["VAR=VALUE" ...]
directory := dir, working directory
stdin-redirection := boolean, standard input redirection
stdout-redirection := boolean, standard output redirection
stderr-redirection := boolean, standard error redirection
pseudo-terminal := boolean, terminal or pipes (UNIX)
show-console := boolean, show or hide console (Windows)
Synchronously runs cmd in a subprocess, where cmd is expected to be a list consisting of a path to an executable on the filesystem and its arguments.
The following keyword settings are available:
- coprocess: A procedure that specifies how to interact with the process,
which it receives as an argument, and what should be returned from
run-process
. Defaults to reading the whole output as a string viastd/misc/ports#read-all-as-string
. - check-status: Declares how to handle the exit status of the process upon
termination. If a procedure is provided, then it will be called with the
process' exit status and a list of process creation arguments. If
check-status is
#t
, the default, then the exit status is checked and an error is raised in case it differs from0
. Lastly, the exit status is simply ignored, when check-status is#f
. - environment: Indicates the set of environment variable bindings that the
process receives. Each element of the list is a string of the form
VAR=VALUE
, where VAR is the name of the variable and VALUE is its binding. When environment is#f
, which is the default, the process inherits the environment variable bindings of the Scheme program. - directory: Sets the working directory of the process. When it's
#f
, the default, then the process uses the value of(current-directory)
. - stdin-redirection: Indicates how the standard input of the process is
redirected. The default
#t
will redirect the standard input from the process-port (i.e. what is written to the process-port will be available on the standard input).#f
will leave the standard input as-is, which typically results in input coming from the console. - stdout-redirection: Indicates how the standard output of the process is
redirected. The default
#t
will redirect the standard output to the process-port (i.e. all output to standard output can be read from the process-port).#f
will leave the standard output as-is, which typically results in the output going to the console. - stderr-redirection: Indicates how the standard error of the process is
redirected.
#t
will redirect the standard error to the process-port (i.e. all output to standard error can be read from the process-port). The default#f
will leave the standard error as-is, which typically results in error messages being output to the console. - pseudo-terminal: Applies to UNIX. It indicates what type of device will be
bound to the process’ standard input and standard output.
#t
will use a pseudo-terminal device (this is a device that behaves like a tty device even though there is no real terminal or user directly involved). The default#f
will use a pair of pipes. The difference is important for programs which behave differently when they are used interactively, for example shells. - show-console: Applies to Microsoft Windows. It controls whether the
process’ console window will be hidden or visible. The default value of this
setting is
#f
(i.e. hide the console window).
More information can be found in section 17.7.2 Process devices
of the Gambit
manual.
Examples:
> (run-process ["date" "--utc"] coprocess: read-line)
"Tue 21 May 2019 12:22:20 PM UTC"
> (run-process ["/usr/bin/ls"])
"desktop\ndev\ndocuments\ndownloads\nmusic\nnotes\npictures\nvideos\n"
> (import :std/misc/ports)
> (run-process ["ls" "-l"] coprocess: read-all-as-lines)
("drwxr-xr-x. 2 user user 4096 Mar 26 13:26 desktop"
"drwxr-xr-x. 8 user user 4096 May 13 14:28 dev"
"drwxr-xr-x. 12 user user 12288 May 19 17:26 documents"
"drwxr-xr-x. 2 user user 4096 May 20 10:13 downloads"
"drwxrwxr-x. 8 user user 4096 May 1 15:13 music"
"drwxr-xr-x. 2 user user 4096 May 21 10:53 notes"
"drwxr-xr-x. 9 user user 4096 Apr 30 19:08 pictures"
"drwxrwxr-x. 3 user user 12288 May 21 09:41 videos")
> (def (word-count path)
(run-process ["wc" path]
coprocess: (lambda (process)
(with ([l w c] (filter number? (read-all process)))
(displayln "lines: " l "\nwords: " w "\nchars: " c)))))
> (word-count "/home/user/dev/scheme/nums.txt")
lines: 5
words: 5
chars: 10
run-process/batch
(run-process/batch cmd) -> void
cmd := list of strings, [path . arguments]
Runs a batch process with stdin closed, and both stdout and stderr on the
current console. Same as (run-process cmd coprocess: close-output-port stdout-redirection: #f)
.
Examples:
> (def files ["file1.txt" "file2.txt" "file3.txt"])
> (for-each (lambda (file) (run-process/batch ["touch" file])) files)
> (run-process/batch (append ["zip" "big.zip"] files))
adding: file1.txt (stored 0%)
adding: file2.txt (stored 0%)
adding: file3.txt (stored 0%)
Simple Queues
To use the bindings from this module:
(import :std/misc/queue)
make-queue
(make-queue) -> queue
Creates a new empty queue, a First-In-First-Out (FIFO) data structure similar to a list. Compared to an ordinary lists, queues allow appending elements without having to walk all to the end first.
Examples:
> (import :std/test)
> (let (q (make-queue))
(check (queue-empty? q) => #t)
(enqueue! q 1)
(enqueue! q 2)
(enqueue! q 3)
(check (queue-length q) => 3)
(queue->list q))
... check (queue-empty? q) is equal? to #t
... check (queue-length q) is equal? to 3
(1 2 3)
queue?
(queue? q) -> boolean
q := queue to check
Returns #t
if q is a queue, #f
otherwise.
Examples:
> (let (q (make-queue))
(enqueue! q (make-queue))
(and (queue? q)
(queue? (queue-peek q))
(queue? (make-queue))))
#t
> (queue? (list 1 2 3))
#f
queue-length
(queue-length q) -> fixnum
q := queue to check
Returns the number of elements enqueued in q.
Similar to retrieving the length of a vector, this operation is O(1)
, since
queues always keep track of their length.
Examples:
> (let (q (make-queue))
(enqueue! q #\a)
(enqueue! q #\b)
(enqueue! q #\c)
(queue-length q))
3
> (queue-length (make-queue))
0
queue-empty?
(queue-empty? q) -> boolean
q := queue to check
Returns #t
if q has no elements enqueued, #f
otherwise.
Examples:
> (queue-empty? (make-queue))
#t
> (let (q (make-queue))
(enqueue! q (make-queue))
(and (queue-empty? (queue-peek q))
(queue-empty? q)))
#f
non-empty-queue?
(non-empty-queue? q) -> boolean
q := queue to check
Returns #t
if q is not empty, i.e., it has at least one element enqueued.
Equivalent to (not (queue-empty? q))
.
Examples:
> (non-empty-queue? (make-queue))
#f
> (let (q (make-queue))
(enqueue! q [])
(non-empty-queue? q))
#t
enqueue!
(enqueue! q elem) -> unspecified
q := queue to push onto
elem := element to append to q
Enqueues (pushes) elem to the end of q. Similar to set!
, it's unspecified
what enqueue!
returns afterwards.
This operation is faster than simply appending to the end of a regular list, because queues needn't be walked first.
Examples:
> (let (q (make-queue))
(enqueue! q 10)
(enqueue! q 20)
(enqueue! q 30)
(queue->list q))
(10 20 30)
> (import :std/srfi/1 :std/test)
> (let ((q (make-queue))
(lst (iota 10)))
(for-each (cut enqueue! q <>) lst)
(check-equal? (queue-length q) (length lst))
(check-equal? (queue->list q) lst))
... check (queue-length q) is equal? to 10
... check (queue->list q) is equal? to (0 1 2 3 4 5 6 7 8 9)
enqueue-front!
(enqueue-front! q elem) -> unspecified
q := queue to push onto
elem := element to enqueue to q
enqueue-front!
is similar to enqueue!
, but pushes elem to the front of q
instead of the end. It's unspecified what will be returned.
Examples:
> (let (q (make-queue))
(enqueue-front! q 10)
(enqueue-front! q 20)
(enqueue-front! q 30)
(queue->list q))
(30 20 10)
dequeue!
(dequeue! q [default = absent-obj]) -> any | default | error
q := queue to pop from
default := optional element returned when q is empty
Pops the front of q and returns that value. If q is empty and a default value is supplied, then default is returned. Otherwise an error is raised.
Examples:
> (let (q (make-queue))
(for-each (cut enqueue! q <>) [1 2 3])
(displayln (dequeue! q))
(displayln (dequeue! q))
(displayln (queue->list q))
(displayln (dequeue! q 100))
;; would signal error without default value:
(displayln (dequeue! q 100)))
1
2
(3)
3
100
queue-peek
(queue-peek q [default = absent-obj]) -> any | default | error
q := queue to peek front
default := optional element returned when q is empty
Returns the front value of q without popping it from the queue like dequeue!
does. If q is empty and a default value is supplied, then default is
returned. Otherwise an error is raised.
Examples:
> (def q (make-queue))
> (enqueue! q #\a)
#<queue #8>
> (enqueue! q #\b)
#<queue #8>
> (queue-peek q)
#\a
> (dequeue! q)
#\a
> (queue-peek q)
#\b
> (dequeue! q)
#\b
> (queue-peek q 10)
10
> (queue-peek q)
error
queue->list
(queue->list q) -> list
q := queue to transform into list
Returns a new list of the elements queued in q, in order.
Examples:
> (import :std/srfi/1)
> (let (q (make-queue))
(for-each (cut enqueue! q <>) (iota 100))
(take (queue->list q) 5))
(0 1 2 3 4)
queue
(defsyntax queue)
Queue type, for user-defined generics and destructuring.
Examples:
> (let (q (make-queue))
(enqueue! q 1)
(enqueue! q 'b)
(enqueue! q #\c)
(with ((queue elems back length) q)
(displayln "elements: " elems ", back: " back "\nand holds " length " items")))
elements: (1 b c), back: (c)
and holds 3 items
Red-Black Trees
To use the bindings from this module:
(import :std/misc/rbtree)
make-rbtree
(make-rbtree cmp [root = +empty+]) -> rbtree
cmp := comparison procedure
root := optional tree root element
Creates a new red-black tree, a self-balancing binary search tree variant, similar to an AVL tree. Also usable as an sorted hash-table alternative for small datasets.
The comparison procedure cmp is expected to accept two keys, a and b, and perform a ternary comparison:
- If
(< a b)
, then it must return a negative number. - If
(= a b)
, then it must return 0. - If
(> a b)
, then it must return a positive number.
Examples of comparison procedures: -
, string-cmp
or symbol-cmp
. The latter
two are defined in this module.
Examples:
> (def rbt1 (make-rbtree -))
> (def rbt2 (list->rbtree string-cmp [["one" . 1] ["two" . 2] ["three" . 3]]))
> (rbtree-put! rbt1 1 "one")
> (rbtree-put! rbt1 2 "two")
> (rbtree-put! rbt1 3 "four")
> (rbtree-put! rbt1 4 "four")
> (rbtree-update! rbt1 3 (lambda (x) (when (string=? x "four") "three")))
> (rbtree-remove! rbt1 4)
> (rbtree->list rbt1)
((1 . "one") (2 . "two") (3 . "three"))
> (rbtree->list rbt2)
(("one" . 1) ("three" . 3) ("two" . 2))
> (import :std/iter)
> (for* ((key (in-rbtree-keys rbt2)) (val (in-rbtree-values rbt1)))
(unless (string=? key val)
(displayln key " " val)))
one two
one three
three one
three two
two one
two three
rbtree?
(rbtree? rbt) -> boolean
rbt := rbtree to check
Returns #t
if rbt is an rbtree, #f
otherwise.
Examples:
> (rbtree? (make-rbtree string-cmp))
#t
rbtree-empty?
(rbtree-empty? rbt) -> boolean
rbt := rbtree to check
Returns #t
if rbt is empty, #f
otherwise.
Examples:
> (rbtree-empty? (make-rbtree -))
#t
> (make-rbtree -)
#<rbtree #62>
> (rbtree-put! #62 0 'NULL)
> (rbtree-empty? #62)
#f
rbtree-put!
(rbtree-put! rbt key val) -> unspecified
rbt := rbtree to update
key, val := key-value association to add to rbt
Destructively updates rbt by inserting the key-value association key to
val. Similar to set!
, it's unspecified what rbtree-put!
returns.
Examples:
> (let (rbt (make-rbtree -))
(rbtree-put! rbt 3 'a)
(displayln (rbtree->list rbt))
(rbtree-put! rbt 2 'b)
(displayln (rbtree->list rbt))
(rbtree-put! rbt 4 'c)
(displayln (rbtree->list rbt))
(rbtree-put! rbt 1 'd)
(displayln (rbtree->list rbt)))
((3 . a))
((2 . b) (3 . a))
((2 . b) (3 . a) (4 . c))
((1 . d) (2 . b) (3 . a) (4 . c))
rbtree-put
(rbtree-put rbt key val) -> rbtree
rbt := rbtree to update
key, val := key-value association to add to rbt
rbtree-put
is similar to rbtree-put!
, but functionally updates rbt instead,
returning a new rbtree without modifying rbt.
Examples:
> (let (rbt (make-rbtree -))
(displayln (rbtree->list (rbtree-put rbt 1 'a)))
(displayln "empty? " (rbtree-empty? rbt)))
((1 . a))
empty? #t
rbtree-update!
(rbtree-update! rbt key proc [default = void]) -> unspecified
rbt := rbtree to update
key := key to look up
proc := update procedure, receives previous value
default := optional default value when key not present
Destructively updates rbt by modifying the value associated with key. Looks
up key in rbt and passes the associated value (or default, if key isn't
present) to proc, an updating procedure. Similar to set!
, it's unspecified
what rbtree-update!
returns.
Examples:
> (def rbt (make-rbtree string-cmp))
> (rbtree-update! rbt "one" 1+ 0) ; would signal error without default value
> (rbtree->list rbt)
(("one" . 1))
> (rbtree-put! rbt "two" 2)
> (rbtree-update! rbt "two" 1+)
> (rbtree-update! rbt "one" (cut * 2 <>))
> (rbtree->list rbt)
(("one" . 2) ("two" . 3))
rbtree-update
(rbtree-update rbt key proc [default = void]) -> rbtree
rbt := rbtree to update
key := key to look up
proc := update procedure, receives previous value
default := optional default value when key not present
rbtree-update
is similar to rbtree-update!
, but functionally updates rbt
instead, returning a new rbtree without modifying rbt.
Examples:
> (def rbt (make-rbtree symbol-cmp))
> (rbtree-put! rbt 'a 16)
> (rbtree->list (rbtree-update rbt 'a sqrt))
((a . 4))
> (rbtree->list rbt)
((a . 16))
rbtree-remove!
(rbtree-remove! rbt key) -> unspecified
rbt := rbtree to update
key := key to look up
Destructively updates rbt by removing the value associated with key. rbt
stays unmodified if key isn't present. Similar to set!
, it's unspecified
what rbtree-update!
returns.
Examples:
> (def rbt (make-rbtree symbol-cmp))
> (rbtree-put! rbt 'a 3)
> (rbtree-put! rbt 'b 2)
> (rbtree-put! rbt 'c 1)
> (rbtree-remove! rbt 'b)
> (rbtree-remove! rbt 'd) ; nothing happens
> (rbtree->list rbt)
((a . 3) (c . 1))
rbtree-remove
(rbtree-remove rbt key) -> rbtree
rbt := rbtree to update
key := key to look up
rbtree-remove
is similar to rbtree-update!
, but functionally updates rbt
instead, returning a new rbtree without modifying rbt. If key isn't present,
then rbtree-remove
simply returns rbt instead of allocating a new identical
tree.
Examples:
> (let (rbt (make-rbtree -))
(rbtree-put! rbt 1 "gambit")
(rbtree-put! (rbtree-remove rbt 2) 1 "gerbil") ; rbtree-remove returns rbt
(displayln (rbtree->list rbt))
(rbtree->list (rbtree-remove rbt 1)))
((1 . "gerbil"))
()
rbtree-ref
(rbtree-ref rbt key [default = absent-obj]) -> any | default | error
rbt := rbtree to search in
key := key to look up in rbt
default := optional default value when key not present
Returns the value associated with key in rbt, or default if no association is present; if the default value is omitted then an error is raised.
Examples:
> (def rbt (make-rbtree -))
> (rbtree-put! rbt 1 'a)
> (rbtree-put! rbt 2 'b)
> (rbtree-put! rbt 3 'c)
> (rbtree->list rbt)
((1 . a) (2 . b) (3 . c))
> (rbtree-ref rbt 2 'NONE)
b
> (rbtree-ref rbt 4 'NONE) ; would signal error without default value
NONE
rbtree-get
(rbtree-get rbt key) -> any | #f
rbt := rbtree to search in
key := key to loop up in rbt
Same as (rbtree-ref rbt key #f)
.
Examples:
> (make-rbtree symbol-cmp)
#<rbtree #54>
> (rbtree-put! #54 'C "single C")
> (rbtree-put! #54 'BB "double B")
> (rbtree-put! #54 'AAA "triple A")
> (rbtree-get #54 'BB)
"double B"
> (rbtree-get #54 'D)
#f
rbtree-copy
(rbtree-copy rbt) -> rbtree
rbt := rbtree to copy
Returns a copy of rbt.
Examples:
> (rbtree->list (rbtree-copy (make-rbtree string-cmp)))
()
> (def rbt (make-rbtree string-cmp))
> (rbtree-put! rbt "op" sqrt)
> (rbtree-put! (rbtree-copy rbt) "op" +) ; not overwriting rbt
> (rbtree->list rbt)
("op" . #<procedure #63 sqrt>)
rbtree-for-each
(rbtree-for-each proc rbt) -> void
proc := procedure to be called for each key-value association in rbt
rbt := rbtree to iterate over
Evaluates (proc key val)
for every key-value association in rbt, in
ascending order. Isn't returning anything, executed for its side effects.
Examples:
> (import :std/iter)
> (make-rbtree -)
#<rbtree #66>
> (for ((key [1 2 3 4 5]) (val ["I" "II" "III" "IV" "V"]))
(rbtree-put! #66 key val))
> (rbtree-for-each (cut displayln <> " -> " <>) #66)
1 -> I
2 -> II
3 -> III
4 -> IV
5 -> V
rbtree-for-eachr
(rbtree-for-eachr proc rbt) -> void
proc := procedure to be called for each key-value association in rbt
rbt := rbtree to iterate over
rbtree-for-eachr
is similar to rbtree-for-each
, but evaluates (proc key val)
in descending (reversed) order.
Examples:
> (import :std/iter)
> (make-rbtree -)
#<rbtree #66>
> (for ((key [1 2 3 4 5]) (val ["I" "II" "III" "IV" "V"]))
(rbtree-put! #66 key val))
> (rbtree-for-eachr (cut displayln <> " -> " <>) #66)
5 -> V
4 -> IV
3 -> III
2 -> II
1 -> I
rbtree-fold
(rbtree-fold proc init rbt) -> any
proc := procedure to be called for each key-value association in rbt
init := initial value
rbt := rbtree to iterate over
Folds rbt in ascending order.
proc's signature is expected to look like this: (proc key val prev-intermediate) -> next-intermediate)
. rbtree-fold
returns the last
next-intermediate value produced by proc. Furthermore, prev-intermediate
will be set to init at the beginning.
Examples:
> (let (rbt (make-rbtree -))
(for-each (cut rbtree-put! rbt <> <>) [1 2 3] ["short" "longest" "medium"])
(rbtree-fold (lambda (k v i)
(cond ((> (string-length v) (string-length i)) v)
(else i)))
"tiny" rbt))
"longest"
rbtree-foldr
(rbtree-foldr proc init rbt) -> any
proc := procedure to be called for each key-value association in rbt
init := initial value
rbt := rbtree to iterate over
Where rbtree-fold
folds in ascending order, rbtree-foldr
folds rbt in
descending (reverse) order.
Examples:
> (def rbt (make-rbtree -))
> (rbtree-put! rbt 3 (iota 3))
> (rbtree-put! rbt 4 (iota 4))
> (rbtree-put! rbt 5 (iota 5))
> (rbtree-fold (lambda (k v i) (cons v i)) [] rbt)
((0 1 2 3 4) (0 1 2 3) (0 1 2))
> (rbtree-foldr (lambda (k v i) (cons v i)) [] rbt)
((0 1 2) (0 1 2 3) (0 1 2 3 4))
rbtree->list
(rbtree->list rbt) -> alist
rbt := rbtree
Returns an alist of key-value associations in rbt, in ascending order.
Examples:
> (list->rbtree - [[3 . "drei"] [1 . "eins"] [2 . "zwei"] [4 . "vier"]])
#<rbtree #71>
> (rbtree->list #71)
((1 . "eins") (2 . "zwei") (3 . "drei") (4 . "vier"))
rbtree->listr
(rbtree->listr rbt) -> alist
rbt := rbtree
Returns an alist of key-value associations in rbt, in descending (reverse) order.
Examples:
> (list->rbtree - [[3 . "drei"] [1 . "eins"] [2 . "zwei"] [4 . "vier"]])
#<rbtree #71>
> (rbtree->listr #71)
((4 . "vier") (3 . "drei") (2 . "zwei") (1 . "eins"))
list->rbtree
(list->rbtree cmp lst) -> rbtree
cmp := comparison procedure
lst := alist of key-value associations
Creates a new rbtree from lst, an associative list. make-rbtree
describes
how cmp is expected to look like.
Examples:
> (rbtree->list (list->rbtree symbol-cmp '((n . 3) (c . 2) (f . 1))))
((c . 2) (f . 1) (n . 3))
> (rbtree-empty? (rbtree-remove (list->rbtree - [[0 . #\x]]) 0))
#t
in-rbtree
(in-rbtree rbt) -> iterator
rbt := rbtree to iterate over
Creates an iterator over rbt, yielding key-value values in ascending order.
Examples:
> (import :std/iter)
> (def rbt (list->rbtree - '((1 . a) (2 . b) (3 . c))))
> (for/collect ((values k v) (in-rbtree rbt))
(cons k v))
((1 . a) (2 . b) (3 . c)) ; equivalent to (rbtree->list rbt)
> (for ((values k v) rbt) ; generic :iter overload for rbtree
(displayln k " -> " v))
1 -> a
2 -> b
3 -> c
in-rbtree-keys
(in-rbtree-keys rbt) -> iterator
rbt := rbtree to iterate over
Creates an iterator over rbt, yielding keys in ascending order.
Examples:
> (import :std/iter)
> (def rbt1 (list->rbtree - [[1 . #\a] [2 . #\b] [3 . #\c] [4 . #\d] [5 . #\e]]))
> (def rbt2 (list->rbtree - [[5 . #\a] [4 . #\b] [3 . #\c] [2 . #\d] [1 . #\e]]))
> (for* ((x (in-rbtree-keys rbt1)) (y (in-rbtree-keys rbt2)))
(when (> x y) ; for* is testing all combinations
(displayln (cons x y))))
(2 . 1)
(3 . 1)
(3 . 2)
(4 . 1)
(4 . 2)
(4 . 3)
(5 . 1)
(5 . 2)
(5 . 3)
(5 . 4)
in-rbtree-values
(in-rbtree-values rbt) -> iterator
rbt := rbtree to iterate over
Creates an iterator over rbt, yielding values in ascending order.
Examples:
> (import :std/iter)
> (def rbt (list->rbtree symbol-cmp '((a . (1)) (b . (2 3)) (c . (4 5 6)))))
> (for/fold (i []) (lst (in-rbtree-values rbt))
(append lst i))
(4 5 6 2 3 1)
> (import :std/misc/list)
> (for (lst (in-rbtree-values rbt))
(when (length>=n? lst 3)
(displayln lst)))
(4 5 6)
rbtree
(defsyntax rbtree)
Red-black tree (rbtree) type, for user-defined generics and destructuring.
Examples:
;; Possible rbtree iterator implementation:
> (defmethod (:iter (rbt rbtree))
(in-rbtree rbt))
> (def (in-rbtree rbt)
(def (iterate)
(rbtree-for-each yield rbt))
(in-coroutine iterate))
string-cmp
(string-cmp a b) -> fixnum
a, b := strings to compare
Comparison function for lexicographic string ordering.
Examples:
> (string-cmp "gambit" "gerbil")
-4
> (string-cmp "aaa" "aaa")
0
> (string-cmp "aac" "aaa")
2
> (string-cmp "aa" "aaaa")
-2
> (string-cmp "aaa" "")
3
> (string-cmp "a" "cb")
-2
symbol-cmp
(symbol-cmp a b) -> fixnum
a, b := symbols to compare
Comparison function for lexicographic symbol ordering.
Examples:
> (symbol-cmp 'gerbil 'gambit)
4
> (symbol-cmp 'D 'B)
2
> (symbol-cmp 'func (string->symbol "func"))
0
symbol-hash-cmp
(symbol-hash-cmp a b) -> fixnum
a, b := symbols to compare
Comparison function for symbol ordering based on their hashes; ties are broken by lexicographic ordering.
Examples:
> (symbol-hash-cmp 'gerbil 'gambit)
-173422207
> (displayln (symbol-hash 'a) " vs. " (symbol-hash 'b))
67905836 vs. 118238693
> (symbol-hash-cmp 'a 'b)
-50332857
Sourceable Representation
To use the bindings from this module:
(import :std/misc/repr)
print-representation
(print-representation obj
[port = (current-output-port)]
[options = (current-representation-options)]) -> void
obj := object to print
port := optional output port
options := hash-table, representation options
Prints an evaluable source-code representation of obj to port, which
defaults to (current-output-port)
. That very representation can later be read
back into an equivalent object.
The behaviour of print-representation
can be specialized for new classes of
objects by defining new overloads on the :pr
method, see representable
.
Note: options aren't doing anything as of now, but are reserved for future use.
Examples:
> (import :std/sugar)
> (def lst (list 1 2 3))
> (def vec (vector 1 2 3))
> (def ht (hash (a 1) (b 2) (c 3)))
> (def fn string-append)
> (displayln lst)
(1 2 3)
> (print-representation lst)
[1 2 3] ; without newline, use prn for that
> (displayln vec)
#(1 2 3)
> (print-representation vec)
(vector 1 2 3)
> (displayln ht)
#<table #631>
> (print-representation ht)
(hash (a 1) (b 2) (c 3))
> (displayln fn)
#<procedure #632 string-append>
> (print-representation fn)
string-append
> (call-with-output-string (cut print-representation vec <>))
"(vector 1 2 3)"
> (repr vec) ; better use repr, which prints to string by default:
"(vector 1 2 3)"
> (with-output-to-file "hash.rep" (cut print-representation ht))
$ cat hash.rep ; unix-like command-line
(hash (a 1) (b 2) (c 3))%
> (with-input-from-file "hash.rep"
(lambda () (print-representation (eval (read)))))
(hash (a 1) (b 2) (c 3))
pr
(defalias pr print-representation)
Short for print-representation
.
Examples:
> (pr #(11 22 33))
(vector 11 22 33) ; without a newline
> (pr '((1 . x) (2 . y) (3 . z)))
[[1 'x ...] [2 'y ...] [3 'z ...]]
> (defstruct gerbil (name age greeting))
> (pr (make-gerbil "Cinnamon" 6 "Morning, everyone!"))
(begin0 #634 "#<gerbil #634>") ; unrepresentable by default
prn
(prn obj [port = (current-output-port)]
[options = (current-representation-options)]) -> void
obj := object to print
port := optional output port
options := hash-table, representation options
prn
does the same as pr
or print-representation
, but also follows with a
newline.
Note: options aren't doing anything as of now, but are reserved for future use.
Examples:
> (import :std/sugar)
> (prn (hash-eqv (1 "I") (5 "V") (10 "X") (50 "L")))
(hash-eqv (1 "I") (10 "X") (5 "V") (50 "L")) ; proper newline at the end
> (prn [1 2 [3 4 [5 6 7] 8] 9])
[1 2 [3 4 [5 6 7] 8] 9]
repr
(repr obj [options = (current-representation-options)]) -> string
obj := object to print
options := hash-table, representation options
repr
is similar to print-representation
, but does not take a port as an
argument and instead returns the representation as a string.
Note: options aren't doing anything as of now, but are reserved for future use.
Examples:
> (defstruct node (data next prev))
> (repr (make-node #f #f #f))
"(begin0 #635 \"#<node #635>\")"
> (repr (node-data (make-node #(1 2 3) #f #f)))
"(vector 1 2 3)"
representable
(defclass representable ())
(defmethod {:pr representable} print-unrepresentable-object)
representable
is an abstract mixin class that defines a method for :pr
. By
default, if a class does not provide its own implementation, then
print-unrepresentable-object
will be called.
Examples:
> (defstruct point (x y))
> (def p (make-point 10 20))
> (displayln p)
#<point #4>
> (prn p)
(begin0 #4 "#<point #4>") ; print-unrepresentable-object
> (import :std/format)
> (defmethod {:pr point}
(lambda (self port options)
(fprintf port "(point ~a ~a)"
(point-x self) (point-y self))))
> (prn p)
(point 10 20)
> (let ((p1 (make-point 10 20))
(p2 (eval (with-input-from-string (repr p) read))))
(and (= (point-x p1) (point-x p2))
(= (point-y p1) (point-y p2))))
#t
print-unrepresentable-object
(print-unrepresentable-object obj
[port = (current-output-port)]
[options = (current-representation-options)]) -> void
obj := object to print
port := optional output port
options := hash-table, representation options
print-unrepresentable-object
is a helper function to use as a fallback for
objects that can't otherwise be displayed. Prints a general-purpose escape of
obj, using the #id
syntax and appends a string hint as obtained from the
write
function.
Note: options aren't doing anything as of now, but are reserved for future use.
Examples:
> (import :std/misc/queue)
> (def q (make-queue))
> (enqueue! q 100)
> (prn q)
(begin0 #9 "#<queue #9>") ; calls print-unrepresentable-object
> (print-unrepresentable-object q)
(begin0 #9 "#<queue #9>")
display-separated
(display-separated lst
[port = (current-output-port)]
[prefix: ""]
[separator: " "]
[suffix: ""]
[display-element: display]) -> void
lst := list of objects to print
port := optional output port
prefix := string prefix
separator := string separator
suffix := string suffix
display-element := function that does the actual printing
display-separated
is a helper function that takes lst, a list of objects to
print, an optional output port, and as keywords a prefix string (empty by
default), a suffix string (empty by default), a separator string (defaulting
to a single space " "
), and a display-element function (display
by
default). Displays each element of lst with the given prefix, suffix,
separator and display-element function.
Examples:
> (import :std/sugar)
> (def ht (hash (a 1) (b 2) (c 3)))
> (display-separated (hash-values ht) (current-output-port)
prefix: "(list\n "
suffix: ")"
separator: "\n ")
(list
3
2
1)
;; this module already supports printing list:
> (prn (hash-values ht))
[3 2 1]
Type Descriptor Utilities.
To use the bindings from this module:
(import :std/misc/rtd)
object-type
(object-type obj) -> type | error
obj := object instance
Safe variant of runtime#object-type
. Returns the class of an object; obj
must be an object instance or an error is signaled.
Examples:
> (defstruct point (x y))
> (object-type (make-point 640 480))
#<type #4 point>
> (eq? point::t #4)
#t
> (object-type 12)
error ; not segfaulting like runtime#object-type
type?
(type? typ) -> boolean
typ := type object to check
Returns #t
if obj is a type object, #f
otherwise.
Examples:
> (defstruct point (x y))
> (type? point::t)
#t
> (type? (object-type (make-point -100 100)))
#t
> (type? (make-point 0 0))
#f
type-id
(type-id typ) -> type id | error
typ := type object to inspect
Returns the id of the type object typ. Will signal an error if typ isn't a type object.
Examples:
> (defstruct a ())
> (defclass b ())
> (type-id a::t)
#:a::t45
> (type-id b::t)
#:b::t49
> (type-id (object-type (make-b)))
#:b::t49
type-name
(type-name typ) -> type name | error
typ := type object to inspect
Returns the name of the type object typ. Will signal an error if typ isn't a type object.
Examples:
> (defstruct vec3i (x y z))
> (type-name (object-type (make-vec3i 30 0 15)))
vec3i
type-super
(type-super typ) -> super class | error
typ := type object to inspect
Returns the super class of the type object typ. Will signal an error if typ isn't a type object.
Examples:
> (defstruct A (x y))
> (defstruct (B A) (z))
> (struct-subtype? A::t B::t)
#t
> (type-super B::t)
#<type #5 A>
> (type-super A::t)
#f
type-descriptor-mixin
(type-descriptor-mixin typ) -> list | error
typ := type descriptor to inspect
Safe variant of runtime#type-descriptor-mixin
. Returns the mixins of the type
as a list. typ must be a type descriptor or an error is signaled.
Examples:
> (defclass A (x))
> (defclass (B A) (y))
> (defclass (C A) (z))
> (defclass (D B C) ())
> (type-descriptor-mixin D::t)
(#<type #8 B> #<type #9 C> #<type #10 A>)
> (type-descriptor-mixin B::t)
(#<type #10 A>)
> (type-descriptor-mixin A::t)
()
type-descriptor-fields
(type-descriptor-fields typ) -> fixnum | error
typ := type descriptor to inspect
Safe variant of runtime#type-descriptor-fields
. Returns the number of fields
of the type as a fixnum. typ must be a type descriptor or an error is
signaled.
Examples:
> (defstruct color (r g b a))
> (type-descriptor-fields color::t)
4
type-descriptor-plist
(type-descriptor-plist typ) -> alist | error
typ := type descriptor to inspect
Safe variant of runtime#type-descriptor-plist
. Returns the type properties of
the type as an alist. typ must be a type descriptor or an error is signaled.
Examples:
> (defstruct vec4d (x y z w) final: #t)
> (type-descriptor-plist vec4d::t)
((fields: x y z w) (final: . #t))
type-descriptor-ctor
(type-descriptor-ctor typ) -> symbol | error
typ := type descriptor to inspect
Safe variant of runtime#type-descriptor-ctor
. Returns the constructor ID of
the type as a symbol. typ must be a type descriptor or an error is signaled.
Examples:
> (defclass A (x) constructor: :init!)
> (defmethod {:init! A}
(lambda (self x)
(set! (A-x self) (* x 2))))
> (type-descriptor-ctor A::t)
:init!
type-descriptor-slots
(type-descriptor-slots typ) -> hash-table | error
typ := type descriptor to inspect
Safe variant of runtime#type-descriptor-slots
. Returns the slots of the type
as a hash-table. typ must be a type descriptor or an error is signaled.
Examples:
> (defclass color (r g b a))
> (type-descriptor-slots color::t)
#<table #6>
> (hash->list #6)
((r . 0) (g . 1) (b . 2) (a: . 3) (g: . 1) (b: . 2) (r: . 0) (a . 3))
type-descriptor-methods
(type-descriptor-methods typ) -> hash-table | error
typ := type descriptor to inspect
Safe variant of runtime#type-descriptor-methods
. Returns the methods
associated with the type as a hash-table. typ must be a type descriptor or an
error is signaled.
Examples:
> (defclass A (x) constructor: :init!)
> (defmethod {:init! A}
(lambda (self x)
(set! (A-x self) (* x 2))))
> (type-descriptor-methods A::t)
#<table #11>
> (hash->list #11)
((:init! . #<procedure #12 A:::init!>))
Shared-structure Equality.
To use the bindings from this module:
(import :std/misc/shared)
equal-shared?
(equal-shared? a b) -> boolean
a, b := structures to check
Checks whether a and b, two potentially recursive, cyclic or otherwise infinite shared structures, e.g. trees or graphs, are equal.
Deprecation note:
Gambit 4.9.3 (released 2019-02-05) added similar support for handling shared
structures with equal?
, superseding equal-shared?
.
Shuffling
To use the bindings from this module:
(import :std/misc/shuffle)
shuffle
(shuffle lst) -> list
lst := proper list to shuffle
Creates a pseudo-random permutation of the values in lst and returns a new list. lst will not be modified during this.
Implementation detail: lst is converted to a random-access vector first, which
is then shuffled via vector-shuffle!
, and finally converted back to a proper
list.
Examples:
> (def lst [1 2 3 4 5])
> (shuffle lst)
(2 1 3 5 4)
> (shuffle lst)
(3 4 2 1 5)
> lst
(1 2 3 4 5) ; lst is unaffected by the shuffling
vector-shuffle
(vector-shuffle vec) -> vector
vec := vector to shuffle
Creates a pseudo-random permutation of the values in vec and returns a new vector. vec will not be modified during this.
Implementation detail: vec is copied first, and it is this very copy that is
then shuffled via vector-shuffle!
.
Examples:
> (def vec #(1 2 3 4 5))
> (vector-shuffle vec)
#(2 1 5 4 3)
> (vector-shuffle vec)
#(4 2 5 1 3)
> vec
#(1 2 3 4 5) ; vec is unaffected by the shuffling
vector-shuffle!
(vector-shuffle! vec) -> vector
vec := vector to shuffle
Creates a pseudo-random permutation of the values in vec, but does so in-place, which means that vec will be modified directly instead of allocating a new vector.
Implementation detail: The shuffling is performed according to Sattolo's algorithm, a Fisher-Yates shuffle variant.
Examples:
> (def vec #(1 2 3 4 5))
> (vector-shuffle! vec)
#(3 4 1 5 2)
> (vector-shuffle! vec)
#(3 1 5 4 2)
> vec
#(3 1 5 4 2)
String utilities
To use the bindings from this module:
(import :std/misc/string)
string-trim-prefix
(string-trim-prefix pfix str) -> string
pfix := string prefix to trim
str := string to search in for pfix
If str starts with the given string prefix pfix, then string-trim-prefix
returns the rest of str without pfix. If pfix isn't a valid prefix of
str, return the entire string str instead.
Examples:
> (string-trim-prefix "date:" "date:2019-05-01")
"2019-05-01"
> (string-trim-prefix "$" "100")
"100"
> (map (cut string-trim-prefix ":std/misc/" <>)
[":std/misc/string" ":std/misc/ports" ":std/misc/list"])
("string" "ports" "list")
string-trim-suffix
(string-trim-suffix sfix str) -> string
sfix := string suffix to trim
str := string to search in for sfix
Analog to string-trim-prefix
, but returns the beginning of str without the
string ending sfix.
Examples:
> (string-trim-suffix ".ss" "strings.ss")
"strings"
> (map (cut string-trim-suffix ".txt" <>)
["README.txt" "LICENSE.txt" "CREDITS.txt"])
("README" "LICENSE" "CREDITS")
string-trim-eol
(string-trim-eol str) -> string
str := string to trim
Trims any single end-of-line marker CR
, LF
or CRLF
at the end of str.
Note: string-trim-eol
removes only one end-of-line marker. Use
(string-trim-right str (char-set #\return #\newline))
to remove all of them.
Examples:
> (string-trim-eol "foo\r")
"foo" ; equivalent to (string-trim-suffix +cr+ "foo\r")
> (string-trim-eol "foo\n\n")
"foo\n" ; only a single end-of-line marker is removed
string-split-prefix
(string-split-prefix pfix str) -> (values string string)
pfix := string prefix to split after
str := string to search in for pfix
Split str based on given string prefix pfix, returning both the string part
after the prefix as well as the prefix itself, or both the whole string and ""
in case pfix isn't found in str.
string-split-prefix
is similar to string-trim-prefix
, but also returns the
prefix as a second value.
Examples:
> (string-split-prefix "123" "123abcdef")
"abcdef" ; suffix/rest
"123" ; prefix
> (string-split-prefix "123" "no-numbers-here")
"no-numbers-here"
""
> (import :std/srfi/13)
> (for-each (lambda (brw)
(let-values (((name year) (string-split-prefix (string-take brw 4) brw)))
(displayln "initial release of " name " was " year)))
["2002firefox" "2003safari" "2008chrome" "2015edge"])
initial release of firefox was 2002
initial release of safari was 2003
initial release of chrome was 2008
initial release of edge was 2015
string-split-suffix
(string-split-suffix sfix str) -> (values string string)
sfix := string suffix to split before
str := string to search in for sfix
Analog to string-split-prefix
, but splits str based on a given string suffix
sfix instead.
string-split-suffix
is similar to string-trim-suffix
, but also returns the
suffix as a second value.
Examples:
> (string-split-suffix ".scm" "secret.scm")
"secret" ; prefix/rest
".scm" ; suffix
> (string-split-suffix ".scm" "secret.lisp")
"secret.lisp"
""
string-split-eol
(string-split-eol str) -> string
str := string to split
Analog to string-trim-eol
, but splits one single end-of-line marker off of
str, which is then also returned as a second value.
Examples:
> (equal? +lf+ (values-ref (string-split-eol "foo\n") 1))
#t
string-subst
(string-subst str old new [count: #f]) -> string | error
str := string to perform changes on, won't be modified
old := string, what to remove
new := string, what to insert instead
count := number of substitutions, optional keyword param
Substitutes/replaces string old with string new inside of str. str
itself will not be modified. The procedure expects count to be a valid number
(a fixnum to be precise) or #f
, indicating the number of substitutions to
perform, otherwise an error is signaled.
count #f
: no limit, the defaultcount > 0
: limit replacementscount <= 0
: return input
Examples:
> (string-subst "aabbaaaaabb" "aa" "cc")
"ccbbccccabb" ; single a remains, only replacing pairs
> (string-subst "subst;some;of;these;semicolons" ";" "#" count: 2)
"subst#some#of;these;semicolons"
string-whitespace?
(string-whitespace? str) -> boolean
str := string to check for whitespace characters
Returns true when the string s consists only of whitespace characters.
character string | name |
---|---|
| space |
\n | line feed |
\t | horizontal tab |
\r | carriage return |
\f | form feed |
\v | vertical tab |
Examples:
> (string-whitespace? "")
#t
> (string-whitespace? "\n \t")
#t
random-string
(random-string [len = 10]) -> string | error
len := optional, length of the output string
random-string
returns a string consisting of regex word-boundary
characters ([a-zA-Z0-9_]
). Throws an error if len
is not a fixnum.
Examples:
> (random-string)
"5CfMyYd2Ob"
> (random-string 0)
""
str
(str . xs) -> string
xs := values to be converted and concatenated into a string
str
converts all of its arguments into a single string.
When called without an argument an empty string is returned.
Examples:
> (str)
""
> (str 2.0)
"2.0"
> (str "hello" ", world")
"hello, world"
> (import :std/format :std/misc/repr)
> (defstruct point (x y))
> (def p (make-point 10 20))
> (defmethod {:pr point}
(lambda (self port options)
(fprintf port "(point ~a ~a)"
(point-x self) (point-y self))))
> (str p 'abc [1 2] 3.4E+2)
"(point 10 20)abc[1 2]340.0"
str-format
(str-format v) -> string
v := any value
str-format
takes any value and returns a formatting string, which can be
used by the :std/format
family of procedures. Considers the :pr
method from :std/misc/repr
.
Examples:
> (import :std/format)
> (str-format "hello")
"~a" ; default format
> (str-format 1.2E+2)
"~f" ; inexact number
> (str-format (vector 1 2 3))
"~r" ; object which implements the :pr method of :std/misc/repr
line ending variables
(define +cr+ "\r")
(define +lf+ "\n")
(define +crlf+ "\r\n")
Global line ending convenience definitions.
Synchronized Data Structures.
To use the bindings from this module:
(import :std/misc/sync)
make-sync-hash
(make-sync-hash ht) -> sync-hash
ht := regular non-synced hash-table
Wraps ht, a regular hash-table, and returns a synced variant that supports thread-safe table operations by implicitly locking.
Note: It's discouraged to modify the unwrapped, non-thread-safe ht after this point.
Examples:
> (import :gerbil/gambit/threads :std/iter :std/srfi/1)
> (def (increment! sht)
(for (x (in-range 1000))
(sync-hash-do sht (cut hash-update! <> x 1+ 0))))
> (def sht (make-sync-hash (make-hash-table-eqv)))
> (def threads (for/collect (n (in-range 16))
(spawn-thread (cut increment! sht))))
> (for-each thread-join! threads)
> (sync-hash-do sht
(lambda (ht)
(every (cut = 16 <>)
(hash-values ht))))
#t
sync-hash?
(sync-hash? sht) -> boolean
sht := sync-hash to check
Returns #t
if sht is a sync-hash, #f
otherwise.
Synced variant of hash?
and hash-table?
.
Examples:
> (import :std/sugar)
> (sync-hash? (make-sync-hash (hash)))
#t
> (sync-hash? (make-hash-table))
#f
sync-hash-ref
(sync-hash-ref sht key default) -> any | default
sht := sync-hash to check
key := key to loop up in sht
default := non-optional default value when key not present
Returns the value bound to key in sht, defaulting to default if no such value was bound.
Synced variant of hash-ref
.
Examples:
> (import :std/sugar)
> (def sht (make-sync-hash (hash (एक 1) (दस 10) (सौ 100))))
> (sync-hash-ref sht 'दस 0)
10
> (sync-hash-ref sht 'सहस्र 0)
0
> (sync-hash-ref sht 10 'NONE)
NONE
sync-hash-get
(sync-hash-get sht key) -> any | #f
sht := sync-hash to check
key := key to loop up in sht
Same as (sync-hash-ref sht key #f)
.
Synced variant of hash-get
.
Examples:
> (import :std/sugar)
> (def sht (make-sync-hash (hash (一 1) (十 10) (百 100))))
> (sync-hash-get sht '十)
10
> (sync-hash-get sht '千)
#f
> (sync-hash-get sht 10)
#f
sync-hash-put!
(sync-hash-put! sht key val) -> unspecified
sht := sync-hash to modify
key, val := key-value pair to add to sht
Binds key to val in sht.
Synced variant of hash-put!
.
Examples:
> (import :std/sugar)
> (make-sync-hash (hash))
#<sync-hash #77>
> (sync-hash-put! #77 #\a [1 2 3])
> (sync-hash-put! #77 'a [4 5 6])
> (sync-hash-put! #77 "a" [7 8 9])
> (sync-hash-do #77 (cut hash-values <>))
((1 2 3) (4 5 6) (7 8 9))
sync-hash-remove!
(sync-hash-remove! sht key) -> void
sht := sync-hash to modify
key := key to look up in sht
Removes sht's binding for key.
Synced variant of hash-remove!
.
Examples:
> (import :std/sugar)
> (let (sht (make-sync-hash (hash (a 10) (b 20) (c 30) (d 40))))
(sync-hash-remove! sht 'b)
(sync-hash-remove! sht 'e) ; nothing happens
(sync-hash-do sht
(lambda (ht) (hash-for-each (cut displayln <> " -> " <>) ht))))
a -> 10
d -> 40
c -> 30
sync-hash-key?
(sync-hash-key? sht key) -> boolean
sht := sync-hash to check
key := key to look up in sht
Returns #t
if sht has a binding for key, #f
otherwise.
Synced variant of hash-key?
.
Examples:
> (def sht (make-sync-hash (list->hash-table [[1 . #\a] [2 . #\b] [3 . #\c]])))
> (sync-hash-key? sht 1)
#t
> (sync-hash-key? sht 3)
#t
> (sync-hash-key? sht 4)
#f
sync-hash-do
(sync-hash-do sht proc) -> any
sht := sync-hash to iterate or modify
proc := procedure handling internal hash-table
Allows thread-safe access to the unwrapped regular hash-table of sht by passing it to proc within an implicitly locked scope. Returns whatever proc is returning.
Examples:
> (import :std/sugar)
> (def sht (make-sync-hash (hash (A0 160) (B1 177) (C2 194) (D3 211) (E4 228))))
> (sync-hash-do sht
(lambda (ht)
(hash-fold (lambda (k v i) (+ v i)) 0 ht)))
970
> (sync-hash-do sht (cut hash-put! <> 'C2 0))
> (sync-hash-get sht 'C2)
0
> (sync-hash-do sht hash->list)
((A0 . 160) (B1 . 177) (D3 . 211) (E4 . 228) (C2 . 0))
> (sync-hash-do sht (lambda (ht) (apply max (hash-values ht))))
228
Text Utilities
To use the bindings from this module:
(import :std/misc/text)
include-text
(include-text path) -> string
path := path to file to include, string
Macro that expands to file contents of path at compile-time.
Examples:
> (def vert-shader-src (include-text "/home/user/dev/opengl/minimal.vert"))
;; instead of here strings:
> (def vert-shader-src #<<EOF
#version 420 core
void main(void)
{
gl_Position = gl_Vertex;
}
EOF
)
> vert-shader-src ; same string in both cases
"#version 420 core\n\nvoid main(void)\n{\n gl_Position = gl_Vertex;\n}"
Thread utilities
To use the bindings from this module:
(import :std/misc/threads)
primordial-thread-group
(primordial-thread-group) -> thread-group
Similar to current-thread-group
, but always returns the primordial (main)
thread's group instead.
Examples:
;; same in main thread
> (current-thread-group)
#<thread-group #87 primordial>
> (primordial-thread-group)
#<thread-group #87 primordial>
;; differs in other groups
> (import :gerbil/gambit/threads)
> (def (task)
(displayln (current-thread-group))
(displayln (primordial-thread-group)))
> (thread-join! (spawn/group 'new-group task))
#<thread-group #181 new-group>
#<thread-group #87 primordial>
thread-group->thread-list*
(thread-group->thread-list* tg) -> list
tg := thread-group to transform
Similar to thread-group->thread-list
, but also collects the threads in all
subgroups of tg. Returns a flat list of threads.
Examples:
> (import :gerbil/gambit/threads)
> (let* ((g1 (make-thread-group 'G1))
(g2 (make-thread-group 'G2 g1))
(g3 (make-thread-group 'G3 g2))
(t1 (make-thread (cut 1) 'T1 g1))
(t2 (make-thread (cut 2) 'T2 g1))
(t3 (make-thread (cut 3) 'T3 g2))
(t4 (make-thread (cut 4) 'T4 g3)))
(displayln (thread-group->thread-list g1))
(displayln (thread-group->thread-list g2))
(displayln (thread-group->thread-list g3))
(displayln (thread-group->thread-list* g1)))
(#<thread #214 T1> #<thread #215 T2>)
(#<thread #216 T3>)
(#<thread #217 T4>)
(#<thread #217 T4> #<thread #216 T3> #<thread #214 T1> #<thread #215 T2>)
all-threads
(all-threads) -> (list thread ...)
Same as (thread-group->thread-list* (primordial-thread-group))
. Walks all
thread-groups recursively and collects threads in a flat list.
Examples:
> (all-threads)
(#<thread #205 th1>
#<thread #202 th5>
#<thread #203 th4>
#<thread #204 th3>
#<thread #201 th2> ; th2, ..., th5 in subgroups
#<thread #1 primordial>)
;; non-recursive, only threads in specified top group:
> (import :gerbil/gambit/threads)
> (thread-group->thread-list (current-thread-group))
(#<thread #1 primordial> #<thread #205 th1>)
thread-dead?
(thread-dead? th) -> boolean
th := thread to check
Returns #t
if th is terminated, i.e., no longer able to run, #f
otherwise.
Examples:
> (let (th (spawn thread-sleep! 2))
(displayln (thread-dead? th))
(thread-join! th)
(displayln (thread-dead? th)))
#f
#t
> (thread-dead? (current-thread))
#f
thread-group-kill!
(thread-group-kill! tg) -> void | error
tg := thread-group to kill
Kills all threads and subgroups in tg. In addition, it detaches the thread group from its parent, making it unreachable from the primordial thread-group structure and eligible for garbage collection. Signals an error when tg contains the current thread.
Note: A thread group that has been killed should not be used again to spawn threads in it.
Examples:
> (def (fib n)
(cond ((< n 2) n)
(else (+ (fib (- n 1))
(fib (- n 2))))))
> (let (g (make-thread-group 'new-group))
(spawn-thread (cut fib 10) 'fib10 g)
(spawn-thread (cut fib 30) 'fib30 g)
(spawn-thread (cut fib 50) 'fib50 g)
(spawn-thread (cut fib 70) 'fib70 g)
(thread-sleep! 1)
(displayln (all-threads))
(thread-group-kill! g)
(displayln (all-threads)))
;; thread fib10 already terminated:
(#<thread #231 fib70> #<thread #232 fib50> #<thread #233 fib30> #<thread #1 primordial>)
(#<thread #1 primordial>)
> (thread-group-kill! (current-thread-group))
error
thread-raise!
(thread-raise! th obj) -> void | error
th := thread to signal error in
obj := exception object to raise
Interrupts th, which can be the primordial thread, and raises obj, terminating that very thread if not handled properly.
Examples:
> (import :gerbil/gambit/threads)
> (spawn thread-sleep! 10)
#<thread #238>
> (thread-dead? #238)
#f
> (thread-raise! #238 'failure) ; #<thread #238> silently terminates
> (thread-dead? #238)
#t
> (thread-raise! (current-thread) 'failure)
*** ERROR IN (console)@1819.1 -- This object was raised: failure
thread-abort!
(thread-abort! th) -> void | error
th := thread to abort
Same as (thread-raise! th +thread-abort+)
. +thread-abort+ is an
internal exception object that has predefined support for type checking via
thread-abort?
and a display-exception
method overload.
Examples:
> (import :gerbil/gambit/threads :std/sugar)
> (def (task)
(try
(let loop ()
(displayln "working")
(thread-sleep! 0.2)
(loop))
(catch (thread-abort? ex)
(display-exception ex (current-error-port)))))
> (let (t (spawn task))
(thread-sleep! 1)
(thread-abort! t)
(thread-join! t))
working
working
working
working
working
thread aborted
thread-abort?
(thread-abort? ex) -> boolean
ex := exception to check
Returns #t
if ex is a thread-abort exception type, #f
otherwise,
essentially checking whether the current thread was interrupted via
thread-abort!
.
Examples:
> (import :gerbil/gambit/threads :std/sugar)
> (try
(thread-abort! (current-thread))
(catch (thread-abort? ex)
(display-exception ex (current-error-port))))
thread aborted ; predefined exception message for thread-abort objects
thread-async!
(thread-async! th proc) -> any | void
th := thread to interrupt
proc := thunk, procedure to execute
Interrupts th, which can be the primordial thread, and executes proc.
Returns the result of proc, if th is the current thread, void
otherwise.
Examples:
> (import :gerbil/gambit/threads)
> (def (task)
(let loop ((i 0))
(thread-sleep! 0.25)
(when (<= i 5)
(displayln "regular work: " i)
(loop (1+ i)))))
> (let (th (spawn task))
(thread-sleep! 1)
(thread-async! th (cut displayln "async work: " (current-thread)))
(thread-join! th))
regular work: 0
regular work: 1
regular work: 2
async work: #<thread #60> ; non-primordial thread
regular work: 3
regular work: 4
regular work: 5
on-all-processors
(on-all-processors proc) -> (list thread ...)
proc := thunk, procedure to execute on all CPU cores
Executes proc multiple times, once on each available processing unit (CPU core), and returns a list containing the created threads.
Note: Runtime support for multiple OS threads needs to be enabled in Gambit (see the installation instructions), otherwise only a single core is used.
Examples:
;; check processor support:
> (##current-vm-processor-count)
2
> (import :gerbil/gambit/threads)
> (let (threads (on-all-processors (lambda () 'OK)))
(map thread-join! threads))
(OK OK)
Timeouts
To use the bindings from this module:
(import :std/misc/timeout)
make-timeout
(make-timeout t [def = absent-obj]) -> time object | def | error
t := real number in seconds | time object | #f (false value)
def := default value returned in case t is #f
Creates a time object representing a time point relative to the current time.
These time objects are used as timeout input parameters for synchronization
primitives in modules such as :gerbil/gambit/threads
or :std/misc/channel
:
(thread-sleep! timeout)
(thread-join! thread [timeout [timeout-val]])
(mutex-lock! mutex [timeout [thread]])
(mutex-unlock! mutex [condition-variable [timeout]])
(channel-put channel value [time-object])
(channel-get channel [time-object [default]])
make-timeout
expects t to be exact or inexact real number in seconds; a time
point object satisfying time?
, in which case it returns t itself; or #f
,
which is often the case for gerbil's internal usage of make-timeout
, returning
the optional default parameter def instead.
Signals an error when t is something other than a real number, a time object or #f
.
Examples:
> (import :gerbil/gambit/threads)
> (thread-sleep! (make-timeout 10))
; no output, but will take ten seconds to complete
UUIDs
To use the bindings from this module:
(import :std/misc/uuid)
Bindings to generate, recognize, and convert from and to universally unique identifiers (UUID) for identification purposes.
Example UUID: dae92f43-4a98-fde7-f559-c2b4c2665138
.
UUID
(UUID uuid) -> uuid | error
(UUID str) -> uuid | error
(UUID vec) -> uuid | error
(UUID sym) -> uuid | error
uuid := uuid object, will be returned
str := string representation to convert from
vec := u8vector representation to convert from
sym := symbolic representation to convert from
Creates a new uuid object from various input objects. It accepts the following inputs:
- an already constructed object, which will then be simply returned,
- a string,
- an u8vector of 16 elements,
- and finally, a symbol that's first converted to a string and processed further.
UUID
signals an error when any of these inputs are invalid UUID representations.
Examples:
> (def ustr (uuid->string (random-uuid)))
> (uuid=? (UUID ustr)
(UUID (string->symbol ustr)))
#t
uuid-length
(def uuid-length 16)
Variable that holds the number of octets that are making up the UUID.
make-uuid
(make-uuid vec str) -> uuid
vec := uuid object u8vector representation
str := uuid object string representation
Creates a new uuid object from vec, a u8vector representation, and str, a
string-representation. If str is specified as #f
, then the string
representation will be generated on demand by procedures like uuid->string
.
Examples:
;; possible random-uuid implementation:
> (import (only-in :std/crypto/etc random-bytes!))
> (def (random-uuid)
(let (bytes (make-u8vector uuid-length))
(random-bytes! bytes)
(make-uuid bytes #f)))
> (uuid->string (random-uuid))
"e26a747f-2a17-aad1-2cdf-504a25e98d02"
uuid?
(uuid? uuid) -> boolean
uuid := uuid object to check
Checks whether uuid is an actual uuid object.
Examples:
> (uuid? "337649b0-ec36-6c80-5d06-53f5586e6322")
#f
> (uuid? (string->uuid "337649b0-ec36-6c80-5d06-53f5586e6322"))
#t
uuid=?
(uuid=? u1 u2) -> boolean
u1, u2 := uuid object to compare
Compares the uuid objects u1 and u2 and checks whether these two are equal.
Two uuid objects are equal when their byte representations are equal?
.
Examples:
> (uuid=? (string->uuid "98ac7df1-204a-7932-dfdf-f466f9c9acff")
(string->uuid "a8a695fd-7d0e-bd55-46a8-5a5951500b4b"))
#f
> (def u1 (u8vector->uuid #u8(227 124 73 208 223 79 147 3 57 65 100 56 99 245 171 80)))
> (def u2 (string->uuid "e37c49d0-df4f-9303-3941-643863f5ab50"))
> (uuid=? u1 u2)
#t
uuid-hash
(uuid-hash uuid) -> fixnum
uuid := uuid object to hash
Returns the hash number of uuid, which is a small exact integer (fixnum). Two
uuid objects have the same hash value when their byte representations are
equal?
.
Examples:
> (uuid-hash (random-uuid))
318443048
> (def u1 (u8vector->uuid #u8(227 124 73 208 223 79 147 3 57 65 100 56 99 245 171 80)))
> (def u2 (string->uuid "e37c49d0-df4f-9303-3941-643863f5ab50"))
> (= (uuid-hash u1) (uuid-hash u2))
#t
uuid->u8vector
(uuid->u8vector uuid) -> u8vector
uuid := uuid object to convert
Converts uuid to its byte vector representation of length uuid-length
.
Examples:
> (uuid->u8vector (random-uuid))
#u8(70 222 137 224 229 154 122 182 255 30 187 147 111 209 17 29)
u8vector->uuid
(u8vector->uuid vec) -> uuid | error
vec := u8vector to convert
Converts vec, a u8vector representing a UUID, to an actual uuid object.
Signals an error when vec's length doesn't match uuid-length
, which is
predefined to be 16.
Examples:
> (def vec #u8(159 202 105 225 118 206 224 215 234 228 189 63 150 185 213 53))
> (u8vector-length vec)
16
> (uuid->string (u8vector->uuid vec))
"9fca69e1-76ce-e0d7-eae4-bd3f96b9d535"
uuid->string
(uuid->string uuid) -> string
uuid := uuid object to convert
Converts uuid to its easier to read string representation.
Examples:
> (uuid->string (random-uuid))
"c6bc8cd8-88c9-64fb-bddb-2bed2c663ed7"
> (uuid->string (string->uuid "ce2c3a97-504c-0926-dddd-0d2571b9a683"))
"ce2c3a97-504c-0926-dddd-0d2571b9a683"
string->uuid
(string->uuid str) -> uuid | error
str := string representing a UUID to convert
Converts str, a hex string representing a UUID, to an actual uuid object. Signals an error when str is malformed.
Examples:
> (string->uuid "39fc54b8-6c00-9ec7-638f-4bb2b83abb0a")
#<uuid #386>
> (uuid->string (string->uuid "ce2c3a97-504c-0926-dddd-0d2571b9a683"))
"ce2c3a97-504c-0926-dddd-0d2571b9a683"
random-uuid
(random-uuid) -> uuid
Generates a new pseudo-random UUID.
Examples:
> (uuid->string (random-uuid))
"eb362448-93bb-f69f-9a90-e5eec79bc0a2"
> (uuid->string (random-uuid))
"78a9cf92-d3c0-eb38-cd16-6a18251ef3f6"
Functional utilities
To use the bindings from this module:
(import :std/misc/func)
Collection of mixed purpose higher-order functions.
always
(always val) -> procedure
(always proc [arg ...]) -> procedure
val := value that should always be returned
proc := procedure that should always be called
arg ... := optional arguments that will be passed to proc
Creates a lambda which returns the same val
or calls always the
same proc
with the same optional args
.
Examples:
> (def fn (always 5))
> (list (fn) (fn) (fn)))
(5 5 5)
> (def fn (always random-integer 10))
> [(fn) (fn) (fn)]
(4 3 8)
repeat
(repeat val n) -> list
(repeat proc n [arg ...]) -> list
val := value that should be repeated
proc := proc that should be called n times
n := exact number, repetitions
arg ... := optional arguments that will be passed to proc
Repeat val
or call proc
with the optional args
n
times and return the
result as list. n
is expected to be an exact number.
Examples:
> (repeat 2 5)
(2 2 2 2 2)
> (repeat (lambda () 10) 3)
(10 10 10)
> (repeat random-integer 3 10)
(8 3 5)
compose1
(compose1 f1 f ...) -> procedure
f1, f ... := procedures
Composes a sequence of unary functions; per the mathematical function composition the value flows right-to-left.
rcompose1
(rcompose1 f1 f ...) -> procedure
f1, f ... := procedures
Like compose1
, but the value flows left-to-right.
compose
(compose f1 f ...) -> procedure
f1, f ... := procedures
Like compose1
, but the composed function accepts multiple arguments.
Note: If you are composing unary functions, use compose1
, as it avoids allocation
from capturing arguments for apply.
rcompose
(rcompose f1 f ...) -> procedure
f1, f ... := procedures
Like compose
, but the values flow left-to-right.
compose/values
(compose/values f1 f ...) -> procedure
f1, f ... := procedures
Like compose
, but the composed function accepts multiple arguments and all functions
can return multiple values, which are then applied as arguments to the next function in
the sequence.
rcompose/values
(rcompose/values f1 f ...) -> procedure
f1, f ... := procedures
Like compose/values
, but the values flow left-to-right.
Functional composition macros
(@compose1 f1 f ...)
(@compose f1 f ...)
(@compose/values f1 f ...)
(@rcompose1 f1 f ...)
(@rcompose f1 f ...)
(@rcompose/values f1 f ...)
These are macro versions of the functional composition operators; they generate significantly more efficient code and allow the optimizer to see through the composition.
every-of
(every-of preds v [test: equal?]) -> boolean
preds := list of predicates or values
v := value to compare with predicates
test := optional, if preds contains a value, test is used for comparison
every-of
returns #t
if all predicates match. If preds
contains a
non-predicate, it is transformed into one using equal?
as test if not
overridden by the test:
keyword.
Examples:
> (every-of [number? fixnum?] 2)
#t
> (every-of [] 1)
#t
> (every-of [number? 10] 10 test: =)
#t
any-of
(any-of preds v [test: equal?]) -> boolean
preds := list of predicates or values
v := value to compare with predicates
test := optional, if preds contains a value, test is used for comparison
any-of
returns #t
if one predicate matches. If preds
contains a
non-predicate, it is transformed into one using equal?
as test if not
overridden by the test:
keyword.
Examples:
> (any-of [number? symbol?] 'a)
#t
> (any-of [] 1)
#f
> (any-of ['a 'b] 'b test: eq?)
#t
pred-limit
(pred-limit pred limit) -> procedure
pred := predicate
limit := number of times pred is allowed to return a truthy value
pred-limit
returns a predicate which returns a truthy value only limit
times,
if limit
is not false.
Examples:
> (filter (pred-limit even? 1) [1 2 3 4 5 6])
(2)
(def (myfilter pred list (limit #f))
(filter (pred-limit pred limit) list))
> (myfilter even? [1 2 3 4 5 6])
(2 4 6)
> (myfilter even? [1 2 3 4 5 6] 2)
(2 4)
> (myfilter even? [1 2 3 4 5 6] 0)
()
pred-sequence
(pred-sequence lst [limit = #f]) -> procedure
lst := proper or circular list
limit := optional, return #t only limit times
pred-sequence
returns a predicate which returns #t
on the last element
of a matching sequence. The list elements are compared using equal?
.
#t
is returned limit
times, if limit
is not #f
.
Examples:
> (import (only-in :std/srfi/13 string-count))
> (string-count "ab_ab" (pred-sequence [#\a #\b]))
2
> (let (fn (pred-sequence [1 2]))
(fn 0) ; #f
(fn 1) ; #f
(fn 2)) ; #t
pred-and
(pred-and pred) -> procedure
pred := predicate
pred-and
returns #t
when every pred
invocation returned a truethy value.
Examples:
> (let (fn (pred-and number?))
(fn 10)
(fn 20))
#t
pred-or
(pred-or pred) -> procedure
pred := predicate
pred-or
returns #t
when any pred
invocation returned a truethy value.
Examples:
> (let (fn (pred-or number?))
(fn 'a)
(fn 20)
(fn "b"))
#t
pred-every-of
(pred-every-of preds [test: equal?]) -> procedure
preds := list of predicates or values
test := optional, if preds contains a value, test is used for comparison
pred-every-of
returns a predicate which returns #t
if all predicates match.
If preds contains a non-predicate, it is transformed into one using equal?
as test if not overridden by the test:
keyword.
Examples:
> (filter (pred-every-of [number? fixnum?]) [1 'a 2.0 "b" 30])
(1 30)
pred-any-of
(pred-any-of preds [test: equal?]) -> procedure
preds := list of predicates or values
test := optional, if preds contains a value, test is used for comparison
pred-any-of
returns a predicate which returns #t
if one predicate matches.
If preds contains a non-predicate, it is transformed into one using equal?
as test if not overridden by the test:
keyword.
Examples:
> (filter (pred-any-of [number? fixnum? "b"]) [1 'a 2.0 "b" 30])
(1 2. "b" 30)
> (import :std/sugar)
> (def files (directory-files))
> (filter (is path-extension (pred-any-of [".jpg" ".jpeg" ".png"])) files)
("xkcd_lisp.jpg" "logo.png")
Extended Real Number Line
The (affine) extended real number line, where real numbers are enriched
with positive and negative infinity, compactifying their order.
Positive infinity is represented by IEEE number +inf.0
while
negative infinity is represented by IEEE number -inf.0
,
so that common operations like <
<=
+
*
, etc., already work.
However, a few operations are provided in a library so that max
and min
functions
work better with infinities. Notably:
(xmin)
will return+inf.0
where(min)
throws an error, and similarly(xmax)
will return-inf.0
where(max)
throws an error.(xmax -inf.0 (+ 1 (expt 2 54)))
will return the exact integer18014398509481985
, where(max -inf.0 (+ 1 (expt 2 54)))
returns the rounded1.8014398509481984e16
. More generally,xmin
andxmax
preserve the type of the argument they return.
To use the bindings from this module:
(import :std/misc/number)
xmin
(xmin <x1> ... <xn>) -> real
xmin
returns the lower bound of the set of its extended real arguments.
In particular, it returns +inf.0
(the positive infinity) if provided zero arguments,
and is the identity function when given a single argument.
xmin/list
(xmin/list <l>) -> real
xmin/list
returns the lower bound of the list of extended real arguments
passed as its arguments. In particular, it returns +inf.0
(the positive
infinity) if provided an empty list.
xmin!
(xmin! <var> <x> ...) -> void
xmin!
side-effects a variable to change it to the xmin
of the previous value and the provided arguments.
xmin/map
(xmin/map <f> <l> [<base>]) -> real
Given a list <l>
or any thing you can iterate on, and a function <f>
,
xmin/map
returns the lower bound of the images by <f>
of the items in <l>
,
and of a <base>
real, by default +inf.0
(the positive infinity).
The function is short-circuiting and will not evaluate further values and their side-effects
after the bottom value -inf.0
is reached.
xmax
(xmax <x1> ... <xn>) -> real
xmax
returns the upper bound of the set of its extended real arguments.
In particular, it returns -inf.0
(the negative infinity) if provided zero arguments,
and is the identity function when given a single argument.
xmax/list
(xmax/list <l>) -> real
xmax/list
returns the lower bound of the list of extended real arguments passed as its arguments.
In particular, it returns -inf.0
(the negative infinity) if provided an empty list.
xmax!
(xmax! <var> <x> ...) -> void
xmax!
side-effects a variable to change it to the xmax
of the previous value and the provided arguments.
xmax/map
(xmax/map <f> <l> [<base>]) -> real
Given a list <l>
or any thing you can iterate on, and a function <f>
,
xmax/map
returns the upper bound of the images by <f>
of the items in <l>
,
and of a <base>
xreal, by default -inf.0
(the negative infinity).
The function is short-circuiting and will not evaluate further values and their side-effects
after the top value +inf.0
is reached.
increment!, pre-increment!, post-increment!, decrement!, pre-decrement!, post-decrement!
(increment! place) -> (void)
(increment! place increment ...) -> (void)
(pre-increment! place) -> number
(pre-increment! place increment ...) -> number
(post-increment! place) -> number
(post-increment! place increment ...) -> number
(decrement! place) -> (void)
(decrement! place decrement ...) -> (void)
(pre-decrement! place) -> number
(pre-decrement! place decrement ...) -> number
(post-decrement! place) -> number
(post-decrement! place decrement ...) -> number
These macro will modify a variable or other place containing a number
to increment (respectively, decrement) its value, adding to it (respectively, subtracting from it)
one (if no further argument is specified) or the provided arguments (if specified).
increment! and decrement! return #!void
,
pre-increment! and pre-decrement! return the value after addition (respectively, subtraction), and
post-increment! and post-decrement! return the value before addition (respectively, subtraction).
make-counter
(make-counter) -> counter
(make-counter n) -> counter
This function creates a new counter, a function that takes zero or more arguments,
adds the sum of these arguments to the counter (or 1
, not 0
, if no argument was provided),
and returns the original value of the counter before the addition.