Usage of Scheme

Michale Erdmann
3.10.2004

Abstract

This document describes the rules and methods to be used when the laguage scheme is to be applied in a project. The intention of this document ist not to advocate for any language but to provide a style and method guide for scheme projects.


Contents

Introduction

The language Scheme

Specification

Implementation

Language Safety

Stucture of scheme applications

Development by contract

User defined Data types

Coding Style

Application Infrastructure

Introduction

This document has been created while i was trying to apply scheme to a small project which had the object to provide an environment which would allow to perform semantic analyis of source code for more or less any language which can be described in a formalized way. Scheme has been selected for basicaly two major reasons:

Since i was using scheme (lisp) serously the first time, but i had no time for an academic aproach of first learning everthing carefully and the sart with the actual work, i had to setup my self a cook book to answer my quiestions about implementing a project in scheme. Perosnally the following topics are important:

I like to point out that i dont like to advocate for any implementation or technique, since i am aware of the fact, that development decisions are made mostly on objective criteria, but the selection of these criterias is based in most cases on subjective experiences. So please keep in mind that the following is only an outsider's view on Scheme.

The language Scheme

Specifications

The roots of the language iare going back to List in the 1970.. Out of this the language scheme has eveolved. The elements of the langauge are defined in a standard called R5RS the Revised5 Report on the Algorithmic Language Scheme. This basic specification if extended constantly by means of the SRFI process (Scheme Requests for Implementation).

Implementations

Even thought that the language is specified it seems that the standard is not sufficient for one or the other reasons. As a consequence there are a lot of different implementations out there. The motor of these implementation are in most cases academic oragnisations. Basically there are two implementation concepts available as interpreter and/or as compiler which translates scheme into C or C++ which in turn is complied an linked. Some implementation are providing some kind of IDE support by providing grafical shells on top of the interpreter. For an overview pls. refere to www.schemers.org.

From these implementations the chicken compiler has been selected since the binding between the dtcall interface of FramerD was easy to implement and a small binding to the fltk toolkit was available. I like to point out that i dont like to advocate for any implementation, since i am aware the selection of a certain development environment might be on objective criterias but the selection of these criterias is based on subjective experiences.

Language Safety

The term safety addresses the issue, that during run time something else happens as expected during coding.

Problem class Example
Misstyped Statements (+1 2) -> blank missing

(a b) -> function name left out

Interface are not typed

Structure of Scheme Application

Implementation Requirement (IR1):

  1. It is possible to export items. Every thing which is not exported is not visible outside of the package (unit)
  2. A unit should define a name space, which means A-X and B-X are denoting different procedures event thought there names have been named in the units equaly.
  3. It has to be possible to track a change of the call interface (number of parameters have changed) for a function by compiling all applications using the unit. A compiler error should occure in such a situation becauer caller and called a assuming different number of paramters.

Unfortunatly the scheme standard does not provide an concept of software modules. Any way most of the implementation are providing such a support each implementation in a sligthly different way. What all systems basicaly agree on is that there are files called units which are using other units.

IR1.1) For the chicken compiler lets assume you have two file main.scm and util.scm. Util.scm provides a function raise-expcetion. The code fragements below is showing how to implement requirement 1.

main.scm

util.scm

(use 'util)

(util-raise-expception 'Error "unspecified")

(declare (unit util)
(export raise-exception .....)

(define (raise-exception ....)

IR1. 2) This can't be implemented in a resonable way with chicken.

IR1. 3) This can't be implemented. Unfortunalty the problem will pop up during runtime of the software system, which makes any software system based on scheme unsave.

Development by Contract

Implementation Requierement (IR2):

  1. A function is called only when the associated contract is full filled. If no matching function is found some kind of run time exception is caused.
  2. Exceptions have to carry different contents depending on the situation.

IR2.1) Scheme does not provide such facilities. The chicken implementation provides only a way to check the contract after the function has been called by means of the assert function, which raises an exception if a certain condition does not match. If the compiler option -unsave is used, these assert functions will have no effect.

IR2.2) Exception may be generated by the application using the functionality srif-12. The code fragment generates an exception which is in fact a vector of two elements, which is in fact the second implementation requirement. The first element gives the exception name and the second element contains some kind of additional diagnistics.

; Rasse an expection with the given excetion-name and message.
(define (raise-exception exception-name exception-message)
   (abort (vector exception-name exception-messagew))
)
                                                                                
; get the exception name
(define (exception-name exn)
   (if (vector? exn) (vector-ref exn 0 ) exn)
)
                                                                                
; get the exception message
(define (exception-message exn)
   (if (vector? exn) (vector-ref exn 1 ) "** None **")

The example above then looks slightly different:

(define (pos-add x y)  
    (assert (> 0 x) (raise-exception 'Contract_Broken (list x y) ) ) (+ x y) )  

(set! catched-exception (handle-exceptions exn
   (begin
      (trace 2 "*** Exception received in move position" ) 
      (trace 2 #( x y z) )
      (trace 5 (backtrace))
      (trace 5 exn )
   )
   
   (print-line (pos-add 1 2))
   (print-line (pos-add -1 -3)))
)
(if (eq? (exception-name catched-exception) 'Contract_Broken ) 
   (print-line catched-exception)
   ;; if the expcetion is completly unknown
   (abort catched-exception)
)

This will yied the output:

merdmann@boavista:~/scheme/w> ./t
"*** Exception received"
#(Contract_Broken (1 2))
merdmann@boavista:~/scheme/w>

Please note that the handle-exceptions works very mutch like the java try..catch .. construct. Any exception not expected will be raised towards the caller, which is very mutch comparable to Ada 95 and Java.

User defined data types

Implementation Requirements (IR3)

  1. Records and variant record's should be available
  2. Classes /Objects concept should be available
  3. Enumerations
  4. Constants

IR3.1) Scheme it self provides only the data type pairs, list, characters, strings and vectors. Records and variant records are missing. In order to overcome this situation the chicken compiler provides a record implementation which is not standard. The usage of this construct should be avoided and vectors should be used.

IR3.2) The concept of objects and classes is available and based of a common dialect (tinyclos). Since here as well the compiler does not provide any means of interface check no advantage will be in using this concept for this project.

IR3.3) Enumerations data types are not available.

The usage of enmeration is either to indicate constant symbolic values to the out side world or to provide a sequence of values. Both usae cases can be simulated in scheme without any major problem. The enumerator type equaly to java can be implemented like this:

(define (make-enumerator . type)    
   (vector 0 (list->vector type)) )  

(define end-of-enum 'eof)  

(define (enum-next enum )    
   (define read-index (vector-ref enum 0))      
   (if (< read-index  (vector-length (vector-ref enum 1)))
       (begin
          (set! result (vector-ref (vector-ref enum 1) read-index))
          (vector-set! enum  0 (+ read-index 1) ))
       (set! result end-of-enum)    )
   result 
)  
 
(define e (make-enumerator 'a 'b 'c 'd ) )  

(do ((i (enum-next e) (enum-next e)))  ((eq? i end-of-enum))
    (write i) (newline) ) 

IR3.4) Constants are not available, but they are not assuemd as a critical issue. An implementation proposal is simply:

(define PI 3.14,,,,)

Coding Style

Implementation Requirements (IR4)

IR4.1) Apply the following rules when selecting names:

(define PI 3.14 A constant
util::copy-file A smybol which is exported by a unit call util. This rule should be applied to all utility packages in a SW system.
get-byte

get-character

get-list

Since scheme does not support dispatching on function arguments, if a unit attempts to exports procedure which the same name but with different input or result types include the type name.
number-of-bytes Avoid abrevations, wich means don't write nbr-of-bytes.

IR4.2) Readablity is a verry difficult term since it depends very mutch of the skills of the person reading code and how mutch time the person can affort for reading. As a general guide line take apply the following rules:

Application Infrastructure

Implementation requirement IR5:

  1. Error Logging and tracing can be activate/deactivated during execution of the software system. Traces should provide different trace levels.
  2. In case errors (exception, asertions etc.) use full diagnostic information need to be written into th logging.
  3. Configuration Parameters have to be stored in a readable text file and changes have to be visible to the system without interrupting the normal operation of the system.

IR5.1) Error logging and trace files can be implemented with the infrastructure of basially any language by using the logging facility of the underlying operating sytem. It should be kept in mind that logging and tracing are used for different purposes. Logging entries are using to indicate the behaviour from the point of view of the end user by logging expected state changes in the software. Traces are to be used by the developer in order to locate the reason of unexpected behavoir of the software system.

For this project a modules called logging is provided which provides a simple interface to the logging of linux. This package provides logging:error, logging:warning and logging::info which allowes the application developer to emit information with different severity levels to the logging system.

IR5.2) This requirement can't be fullfilled by any tool, since the developer needs to decide what is important to find the cause of a problem. The best what can be done is to provide some code as a common debugging facility. An example how this can be done in the example for IR2.2.

IR5.3) Textual configuration files are not problem with scheme since tile io is available. The mechanism to indicate data changes can be done by means of call back which are implemented simply by lambda constructs as shown below:

(register "TraceLevel" 
   (lambda ( id context event )
        (display event) (display " ") (display data) (newline)
        (set! data (+ 1 data)))     
   data )) 

This code fragment will register a piece of handler code for the variable "TraceLevel". The handler takes three input parameters. The config variable, the so called context and an event code. Since there are no abstract data types or protoypes avaiable this convention should be followed though the complete project. The event code gives the reason for the call. The context referes to all state variables related to this configuration variable. In the simplest case this object contains the value of the configuration variable it self.

Since not on all operating platforms it is possible to receive an signal when the contents of a file has changed. the best solution seems to be to read the cfg files regulary (every 2 min) and to check if something has changed.

The configuration subsystem consists of a file reader and file writer and the configuration manager. The reader/writer are input parameters to the configuration manager.


Michael Erdmann