Chapter 9. Additional Libraries
Prev |  | Next

# Chapter 9. Additional 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 `car`s are they keys found in `hashtable` and whose `cdr`s 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.

Prev |  | Next
Chapter 8. Java Interaction | Home | Chapter 10. Modules and Libraries