Chapter 9.  Additional Libraries

Table of Contents

Optional SISC Libraries
Definitions
Bitwise Logical Operations
Records
Hash Tables
Binary Buffers
Procedure Properties
Loadable Scheme Libraries
Operating System Interface
Third-Party Libraries
SRFIs
SLIB
Creating Libraries

Optional SISC Libraries

The optional SISC libraries are modules whose definition is included in the full SISC distribution, but not the lite distribution.

Definitions

Requires: (import misc)

In addition to the standard R5RS definition syntaxes, SISC provides an additional value definition and syntax definition form.

First, define-values, which allows more than one binding to be created at once, given the multiple-value return of its body.

syntax: (define-values (binding binding ... ) expression) => undefined

Evaluates the expression in the body, which must return the same number of values as there are binding names. Each value is then bound (in an undefined order) to each binding name.

define-values behaves like define in terms of which environment the bindings are created. If the define-values statement is at the top-level then bindings are created in the top-level environment. If the statement is in a lexical environment, then it behaves just as an internal define.

Second, define-simple-syntax provides a shorthand for syntax definition when the syntactic form's appearance is similar to a function.

syntax: (define-simple-syntax (name vars ... ) body) => undefined

Creates a syntactic form with the given name, and any number of listed syntactic variables, which expands to the given body (with instances of the syntactic variables hygienically expanded).

Here is an example usage of define-simple-syntax to define the when macro:

(define-simple-syntax (when condition body ...)
  (if condition
      (begin body ...)))
      

Bitwise Logical Operations

Requires: (import logicops)

In addition to the R5RS set of procedures that deal with numbers, SISC provides operators for performing bitwise logic operations on exact integers.

procedure: (logand integer [integer] ...) => integer

Performs the logical AND of all the provided arguments.

procedure: (logor integer [integer] ...) => integer

Performs the logical OR of all the provided arguments.

procedure: (logxor integer [integer] ...) => integer

Performs the logical exclusive-OR of all the provided arguments.

procedure: (lognot integer) => integer

Performs the logical NOT of the provided integer.

procedure: (logcount integer) => integer

Returns the count of the number of 1 bits in the representation of a given positive integer, or 0 bits in a negative integer.

In addition, two operators are provided to perform arithmetic shifts on any integer (these operators do not have the range limitation the previous logical functions do). The shift operators return a newly generated number formed by shifting the provided number left or right by the given number of bits.

procedure: (ashl integer bits) => integer

Arithmetically shifts integer left by bits bits.

procedure: (ashr integer bits) => integer

Arithmetically shifts integer right by bits bits.

Mathematically, if r is the number, and s is the number of bits, ashl calculates:

r x 2s
        

while ashr calculates

r / 2s
        

in the integer domain. Both ashl and ashr operate on exact integers and produce only exact integers.

Records

Requires: (import record)

SISC provides a native implementation of record types as defined in SRFI-9. See http://srfi.schemers.org/srfi-9/ for details. In addition to the define-record-type syntax provided by SRFI-9, a more compact (but less flexible) define-struct syntax is offered.

syntax: (define-struct name (field ...))

Defines a SRFI-9 record type as follows:

(define-record-type (make-name field ...)
  name?
  (field name-field set-name-field!)
  ...)

i.e. naming conventions are used to determine the names of the record type constructor, predicate, field access and field modifier procedures.

Records are eq? and eqv? if and only if they are identical. Records are equal? if and only if they are instances of the same record type and all their fields are equal?.

It is also possible to define non-generative record types, using define-nongenerative-record-type and define-nongenerative-struct. Non-generative record types are associated with a user-specified guid. If an attempt is made to define a record type with a guid that is already bound to an existing record type then the existing record type is modified, instead of a new record type being created. Non-generative record types are serialised specially such that deserialising them also performs this check. By contract, deserialisation of ordinary, generative record types and their instances results in duplicate types being created, which is usually not desirable.

syntax: (define-nongenerative-record-type name guid (constructor-name field ...) (predicate ...) (field-spec ...))

This is the same as define-record-type, except that the resulting record type is non-generative with guid, a symbol, as the unique identifier.

syntax: (define-nongenerative-struct name guid (field ...))

This is the same as define-struct, except that the resulting struct is non-generative with guid, a symbol, as the unique identifier.

Hash Tables

Requires: (import hashtable)

Hash tables store mappings of keys to values. Hence they are similar to association lists, except that hash tables allow retrieval, addition and modification in constant time whereas association lists typically perform these operations in linear time based on the number of elements.

Creation and Introspection

Hash tables are a distinct data type. They can be created empty or filled with the contents of an association list. The converse, creating an association list from a hash table, is also supported.

procedure: (make-hashtable [equivalence-predicate] [hash-function] [thread-safe?] [weak?]) => hashtable

Creates a hash table. The first optional argument supplies the equivalence test procedure that the hashtable should use for comparison of keys. This must be a function accepting two arguments and returning a boolean. It defaults to equal?.

The second optional argument supplies the hash function, which must accept one argument and return a numeric value. For the equivalence predicates eq?, eqv?, equal?, string=?, string-ci=? it defaults to hash-by-eq, hash-by-eqv, hash-by-equal, hash-by-string=, hash-by-string-ci=? respectively, and hash-by-equal otherwise.

The third optional argument determines whether operations on the hash table should be made thread-safe. The default is #t. Thread synchronization (see Chapter 6, Threads and Concurrency ) is required if there are potentially several threads operating concurrently on the hash table and one of these threads performs a structural modification (i.e. adds or removes an entry; merely changing the value of an entry is not a structural modification). Failure to enforce proper thread synchronization has unpredicatable results.

The fourth optional argument determines whether the keys in the hash table are held with weak references, allowing them to be garbage collected, and automatically removed from the hashtable when they are not referenced from elsewhere. The default is #t.

For reasons of disambiguation, the hash function argument can only be supplied if the preceeding equivalence predicate was also supplied, and the weakness argument can only be supplied if the preceeding thread-safety argument was also supplied.

The equivalence and hash function must produce stable results for the keys in a hash table.

The effects of invoking an escaping continuation inside the equivalence predicate or hash function, or invoking a continuation captured inside the equivalence predicate or hash function after that function has returned, are unspecified.

procedure: (alist->hashtable alist [equivalence-predicate] [hash-function] [thread-safe?] [weak?]) => hashtable

Creates a hashtable and initializes it with the keys and values found in alist. alist must be a list of pairs, with the car of each pair representing a key and the cdr representing its associated value. The optional arguments are the same as for make-hashtable.

If there are multiple pairs which contain the same key (with respect to chosen equivalence test) then the resulting hash table will associate the key with the value of the last such pair.

procedure: (hashtable? value) => #t/#f

Returns #t if value is a hash table, #f otherwise.

procedure: (hashtable/equivalence-function hashtable) => procedure

Returns the equivalence predicate of hashtable.

procedure: (hashtable/hash-function hashtable) => procedure

Returns the hash function of hashtable.

procedure: (hashtable/thread-safe? hashtable) => #t/#f

Returns #t if hashtable is thread safe, #f otherwise.

procedure: (hashtable/weak? hashtable) => #t/#f

Returns #t if the keys in hashtable are held by weak references, #f otherwise.

procedure: (hashtable/size hashtable) => number

Returns the number of key/value pairs stored in hashtable.

procedure: (hashtable->alist hashtable) => alist

Returns an association list comprising the elements of hashtable. The list contains pairs whose cars are they keys found in hashtable and whose cdrs contain the associated values.

Hash Functions

Several hash functions that return results consistent with common equivalence predicates are predefined.

procedure: (hash-by-eq value) => number

procedure: (hash-by-eqv value) => number

procedure: (hash-by-equal value) => number

procedure: (hash-by-string= string) => number

procedure: (hash-by-string-ci= string) => number

These procedures return a hash code of their argument that is consistent with eq?, eqv?, equal?, string=?, string-ci=?, respectively.

Access and Modification

All hash table access operations follow a similar pattern. They return the value that was associated with the the given key at the time the operation was invoked. If no binding for the key existed, an optionally supplied value is returned that defaults to #f. This allows the programmer to associate keys with #f values and distinguish this case from not having any association for a key.

procedure: (hashtable/put! hashtable key val [nobinding]) => value

Associates key with val in hashtable. Returns the previous association of key or nobinding, which defaults to #f, if key has no previous association.

procedure: (hashtable/get hashtable key [nobinding]) => value

Returns the value associated with key in hashtable, or nobinding, which defaults to #f, if key has no association.

procedure: (hashtable/get! hashtable key thunk [unsafe?]) => value

Returns the value associated with key in hashtable. If key has no association then thunk is called and the result is associated with key in hashtable and also returned. The unsafe?, which defaults to #t, indicates whether thunk may invoke escaping continuations or raise errors. Setting unsafe? to #f results in more efficient execution but may cause deadlocks if thunk is unsafe. See also mutex/synchronize-unsafe in the section called “ High-level Concurrency ”.

When hashtable is thread-safe this operation is atomic.

procedure: (hashtable/contains? hashtable key) => #t/#f

Returns the #t if hashtable contains an entry for key, #f otherwise.

procedure: (hashtable/remove! hashtable key [nobinding]) => value

Removes the association of key in hashtable. Returns the associated value of key or nobinding, which defaults to #f, if key has no association.

Bulk Operations

Bulk operations are operations that apply to all elements of a hash table.

procedure: (hashtable/clear! hashtable)

Removes all elements from hashtable.

procedure: (hashtable/keys hashtable) => list

Returns the keys contained in hashtable.

procedure: (hashtable/for-each proc hashtable)

Applies proc to each element of hashtable. proc is called with two parameters - the key and the value of the element.

procedure: (hashtable/map proc hashtable) => list

Applies proc to each element of hashtable. proc is called with two parameters - the key and the value of the element. The results of calling proc are returned as a list.

Binary Buffers

Requires: (import buffers)

Binary buffers provide an opaque container for a fixed amount of binary data. The binary buffer library provides a number of functions for creating and accessing those buffers. The buffer is very similar to a vector, in that it is a randomly accessable, zero-based structure. But as a tradeoff for space efficiency, binary buffers are only capable of storing bytes. The bytes are stored as 8-bit, unsigned fixed integers (of the range 0-255).

procedure: (buffer? value) => #t/#f

Returns true if and only if the provided argument is a binary buffer.

procedure: (make-buffer size [fill-value]) => buffer

Creates a new buffer capable of storing size bytes. size must be a fixed non-negative integer. If provided, the value of all bytes in the buffer is initialized to fill-value. If not provided, the contents of the buffer is unspecified.

procedure: (buffer [value] ...) => buffer

Creates a new buffer whose size is equal to the number of arguments given and whose contents are the bytes given as arguments.

procedure: (buffer-length buffer) => fixed integer

Returns the capacity of the given buffer.

procedure: (buffer-ref buffer index) => fixed integer

Returns the byte at offset index in the specified buffer. It is an error if index is out of range.

procedure: (buffer-set! buffer index new-value) => undefined

Sets the byte at offset index of the specified buffer to the given fixed integer new-value. It is an error if index is out of range.

procedure: (buffer-copy! source-buffer source-offset dest-buffer dest-offset [count]) => undefined

Copies count bytes starting from index source-offset in the source buffer to successive bytes starting at index dest-offset in the destination buffer. If count is unspecified, it is assumed to be the length of the source buffer. It is an error to copy more bytes from the source buffer than are available, or to copy more bytes into the destination buffer than its capacity allows.

Buffers are serializable (can exist in loadable libraries or a SISC heap), but are not representable in an s-expression. For this reason, they bear the printed representation of #<buffer>.

Procedure Properties

Requires: (import procedure-properties)

SISC allows key/value bindings to be associated with procedures. This has a number of applications. For instance, generic procedures store their methods in a procedure property.

Keys must be symbols. Values are any valid Scheme value. All operations are thread-safe.

procedure: (procedure-property proc symbol [nobinding]) => value

Returns the value associated with the property symbol of procedure proc, or nobinding, which defaults to #f, if the property is not set.

procedure: (set-procedure-property! proc symbol val [nobinding]) => value

Sets the property symbol of procedure proc to the value val. Returns the previous value of the property or nobinding, which defaults to #f, if the property was unset.

procedure: (procedure-property! proc symbol thunk [unsafe?]) => value

Returns the value associated with the property symbol of procedure proc. If the property is unset then thunk is called and the property is set to the result, which is also returned. The unsafe?, which defaults to #t, indicates whether thunk may invoke escaping continuations or raise errors. Setting unsafe? to #f results in more efficient execution but may cause deadlocks if thunk is unsafe. See also mutex/synchronize-unsafe in the section called “ High-level Concurrency ”.

Loadable Scheme Libraries

Requires: (import libraries) [4]

Scheme code can be packaged into libraries that can have dependencies on other libraries and can be loaded as required. Libraries are identified by a name that follows Java package file naming conventions, i.e. using path-style names typically containing domain, organisation name, project name and library name. For instance, if company Foo produces a library Baz for project Bar and that library contains three files, the file structure might look as follows:

com/foo/bar/baz.scm
com/foo/bar/baz/baz1.scm
com/foo/bar/baz/baz2.scm
com/foo/bar/baz/baz3.scm

This library can be made accessible from SISC by adding the base directory or a jar file containing these files to the Java class path. Libraries are loaded by the following procedure.

procedure: (require-library name) => undefined

Checks whether the library identified by name (a string), has already been loaded and, if not, loads it. An error is raised if the library cannot be found.

Libraries are loaded using the load procedure from a resource located by the find-resource procedure. The name of the resource is derived from the name of the library by appending ".scc", ".sce" and, if that does not succeed, ".scm".

Note that require-library only loads a single file. The definition of dependencies on other libraries and the loading of other files therefore needs to happen within that file. For instance, the file com/foo/bar/baz.scm from the above example might contain the following:

(require-library 'com/foo/bar/boo)
(load "baz/baz1.scm")
(load "baz/baz2.scm")
(load "baz/baz3.scm")
        

It is possible to programmatically check whether a particular library exists and whether it has been loaded:

procedure: (library-exists? name) => #t/#f

Returns #t if the library identified by name (a string) exists, #f otherwise.

procedure: (library-loaded? name) => #t/#f

Returns #t if the library identified by name (a string) has been loaded, #f otherwise.

Operating System Interface

Requires: (import os)

The operating system interface currently contains functions for spawning external processes on the host operating system, obtaining input/output ports to the resulting process, and monitoring their status.

Two procedures exist for spawning processes:

procedure: (spawn-process program/commandline [arglist]) => process

Spawns a process, returning a process handle. If the optional argument list is provided, then the first argument is the binary to run with those arguments. If omitted, the first argument is tokenized as a commandline and used to spawn the process.

procedure: (spawn-process-with-environment program arglist environment [working-directory]) => process

procedure: (spawn-process/env program arglist environment [working-directory]) => process

Spawns a process named by program with the arguments given in arglist, in the given environment. The environment is an association list of strings to strings. The key in the association list is an environment variable name, and the corresponding value is the value to assign to that environment variable. If the environment parameter is #f, the environment variables of the current SISC instance are used.

The optional parameter working-directory specifies the directory which will be set as the current directory when the process is spawned. If ommited, the value of the current-directory parameter (i.e. the current directory of the running Scheme program) is used instead.

procedure: (process? value) => #t/#f

Returns #t if the given value is a process handle.

Once started, a process will run in parallel to the current Scheme program according to the usual scheduling of the host platform. The process handle obtained can be used to obtain the input, output, and error streams of the process using the following functions:

procedure: (get-process-stdout process) => binary-input-port

Returns a binary input port which will read bytes which the given process has written to its standard output stream.

procedure: (get-process-stderr process) => binary-input-port

Returns a binary input port which will read bytes which the given process has written to its standard error stream.

procedure: (get-process-stdin process) => binary-output-port

Returns a binary output port which when written to will send bytes to the given process' standard input stream.

Finally, functions are provided to check the status of a spawned process, and to wait for a process to complete:

procedure: (process-terminated? process) => integer or #f

Checks to see if the given process has terminated, and returns the process' return code if so. If the process is still running, #f is returned.

procedure: (wait-for-process process) => integer or #f

Waits for the given process to terminate, and returns the process' return code if so. The wait operation may be interrupted by other code, in which #f is returned.

Third-Party Libraries

SISC provides hooks for accessing a number of third-party Scheme libraries.

Warning

This functionality has not undergone much testing.

SRFIs

The Scheme Requests For Implementation (SRFI) process aims to coordinate libraries and other additions to the Scheme language between different Scheme implementations. For details see http://srfi.schemers.org/ which describes the process and contains a list of all available SRFIs.

SRFI Modules

In SISC each SRFI is encapsulated in a module. See Chapter 10, Modules and Libraries for details of SISC's module system. The definitions for SRFI modules are not included in the standard SISC heap build and hence must be loaded separately from various compiled library files in the sisc-lib.jar jar file in the root directory of the SISC binary distribution. As long as this jar file is on the classpath, which is the case by default, any SRFI's module definition may be loaded with the expression (require-library 'sisc/libs/srfi/srfi-n), where n is the SRFI's number. For example:

(require-library 'sisc/libs/srfi/srfi-9)
          

All SRFI's may be loaded at once by requiring sisc/libs/srfi.

Using SRFIs

SISC currently supports SRFIs 0, 1, 2, 5, 6, 7, 8, 9, 11, 13, 14, 16, 18, 19, 22, 23, 25, 26, 27, 28, 29, 30, 31, 34, 35, 37, 38, 39, 40, 42, 43, 45, 48, 51, 54, 55, 59, 60, 61, 62, 66, 67, 69 and 78. Once the SRFI module definitions have been loaded as described above, a SRFI n can be imported using

(import srfi-n)
          

e.g.

(import srfi-1)
(xcons 1 2) ;=> (2 . 1)
          

SRFI modules, like all modules in SISC, can be imported/used by other modules. Doing so does not pollute the top-level environment with the definitions exported by the module, i.e. any code outside the importing module remains unaffected.

If, however, an SRFI is to be imported into the top-level, one can use the require-extension mechanism (see the section called “require-extension).

SRFI Extensions

Some SRFIs have built-in extension points that Scheme implementations can use to augment a SRFI's functionality. It is also the case that some SRFIs would benefit from slightly extended APIs.

This section documents the SRFI extensions implemented by SISC.

SRFI 69 (Basic hash tables)

The make-hash-table function takes two additional optional arguments: thread-safe? and weak?. See make-hashtable in the section called “Creation and Introspection” for details.

The basic hash table API is extended in a separate module:

Requires: (import srfi-69-ext)

procedure: (hash-table-thread-safe? hashtable) => #t/#f

Returns #t if hashtable is thread safe, #f otherwise.

procedure: (hash-table-weak? hashtable) => #t/#f

Returns #t if the keys in hashtable are held by weak references, #f otherwise.

procedure: (hash-table-ref! hashtable key thunk) => value

Returns the value associated with key in hashtable. If key has no association then thunk is called and the result is associated with key in hashtable and also returned.

When hashtable is thread-safe this operation is atomic.

procedure: (hash-table-ref! hashtable key default) => value

Returns the value associated with key in hashtable. If key has no association then default is associated with key in hashtable and also returned.

When hashtable is thread-safe this operation is atomic.

SLIB

The SLIB portable scheme library provides compatibility and utility functions for standard Scheme implementations. It is supported by many Schemes, including SISC.

Downloading and Installation

The latest version of SLIB is available from http://swissnet.ai.mit.edu/~jaffer/SLIB.html as both a zip file and RPM. The site also hosts an online version of the SLIB manual.

Download SLIB and install it in a convenient location. The RPM will by default be installed in /usr/share/slib/. Do not worry when you see some errors about missing programs such as mzscheme and scheme48 when installing the RPM - these happen because SLIB tries to auto-configure itself for various Schemes that you may not have installed on your system.

Environment

Using SLIB in SISC requires two Java system properties to be set:

  • sisc.home This should (but does not actually have to) point to the location where you have installed SISC. If you are using one of the scripts from the binary SISC distribution in order to run SISC then this property will automatically be set to the value of the SISC_HOME environment variable.

  • sisc.slib This must point to the location where you installed SLIB. Other Schemes supporting SLIB tend to use an environment variable SCHEME_LIBRARY_PATH, so it is advisable to define that (if it is not already defined) and run Java with a -Dsisc.slib=... option based on the environment variable. If you are using the scripts from the binary $SISC; distribution in order to run SISC then you can set the property by adding the -Dsisc.slib=... to the JAVAOPT environment variable.

    Note

    Note that the value of this property should be a fully qualified url, e.g. file:///usr/share/slib

You need to ensure that all potential users of SLIB have read permissions to files in the directories referred to by the above system properties.

Building the Catalog

Make sure that the above system properties are set and that you have write permissions to the sisc.home directory; often this means you need to be logged in as a privileged user.

Start SISC as you normally would. At the prompt type

(require-library 'sisc/libs/slib)
(require 'new-catalog)
(exit)
          

The above should create a file slibcat in the sisc.home directory. It is a good idea to check that this has indeed happened.

Using SLIB

Make sure the above system properties are set. Start SISC as you normally would. At the prompt load the SISC SLIB as described above, i.e.

(require-library 'sisc/libs/slib)
          

You can now load SLIB modules using require, e.g.

(require 'tsort)
(tsort '((shirt tie belt)
         (tie jacket)
         (belt jacket)
         (watch)
         (pants shoes belt)
         (undershorts pants shoes)
         (socks shoes))
       eq?)
          

loads the topological sorting module and invokes one of the procedures defined by it.

Please refer to the SLIB manual for further details of what modules are available. Note however that, as with most other Schemes supported by SLIB, there will be some modules that are not available or do not work in SISC.

Creating Libraries

Requires: (import libraries)

SISC allows the creation of compiled libraries which contain compiled scheme code. These libraries can then be executed into a running SISC session in order to extend the functionality without processing or possessing the original source. Such libraries can be loaded using load as would any ordinary Scheme source file.

Compiled code files (.scc) are created using the compile-file function, which takes a Scheme source file and a target output file, and processes the Scheme source through the various expansion and compilation phases, and then serializes the resulting SISC microexpressions to the given target file. The resulting file may then be loaded with load as any ordinary Scheme file would, or can be placed in the library path and resolved using require-library.

procedure: (compile-file source-file target-file) => undefined

Compiles the Scheme source present in source-file, writing the resulting micro-expressions into target-file, suitable for loading.

As a side effect, the micro-expressions are also evaluated, i.e. in effect compile-file compiles and evaluates the source-file. The latter is necessary because the compilation of an expression may depend on the results of evaluating a previous expression, e.g. as is typically the case for libraries that depend on other libraries.

.sll Deprecation

Scheme Loadable Libraries (.sll files) were deprecated in version 1.9. This was due to unresolvable incompatibilities in the engine's closure representation and the .sll functionality.



[4] This module gets imported by default.