Pocket Scheme for the H/PC and P/PC - Usage Notes

Entering programs | Debugging | Performance | Tail recursion | I/O and storage management | Foreign function interface | Win32 API calls | Numeric limitations

Entering programs

Most H/PCs have very small keyboards. Most P/PCs lack a keyboard entirely. Entering program text on these devices is a task that can range from the merely tedious to the completely frustrating.

The most expeditious way to enter a program is to perform your data entry on a desktop system with a comfortable display and keyboard, then transfer the resulting program source files to the mobile device for execution and debugging. If you are using a P/PC, keep your files less than 32Kb in size, so you can edit them locally on the mobile device using Pocket Scheme Editor. (The H/PC editor can accommodate larger files.)

When entering expressions on the mobile device itself, keep symbol names short. Introduce briefer synonyms for lengthy procedure names. Pocket Scheme already uses call/cc as a synonym for the excessively long call-with-current-continuation. Once you have declared (define p display), you need never peck out display with your stylus again. Of course, once you have pecked out the full text of display or call-with-current-continuation a few times, the Pocket PC word completion input feature will help you enter subsequent instances of these symbols.

Write short procedures that eliminate long sequences of repetitive calls. Lengthy sequences of calls to display particularly invite this, just because they are otherwise so common: consider (define (cout . args) (for-each display args) (newline)). Where a procedure won't work, consider a macro.

Microsoft Transcriber works very well in conjunction with Pocket Scheme, so long as you are using a Pocket PC with sufficient horsepower. This includes the Compaq iPAQ 3600, but not, in my opinion, the Compaq Aero 1550.

Debugging

The (trace fcn) feature shadows a procedure to print a trace of every call to and return from that procedure. Use untrace to remove this shadowing.

If your code stops with an error, try issuing (debug #t), then reevaluating the offending expression. The run-time debugging feature exacts a substantial performance penalty, so remember to reset it with (debug #f) when you are finished.

Performance

Most H/PCs and P/PCs have very slow displays, constraining the running speed of Pocket Scheme programs by the number of display calls that they make.

Escape continuations (bound by call/ec) are cheaper and faster than reentrant continuations (bound by call/cc). Use them wherever you want a nonlocal exit.

The default Pocket Scheme memory configuration values are fine for most purposes. Generally speaking, applications run more quickly with large pages than with small pages. You may find the (heap-info) command informative when tuning page size. Do not set the maximum heap limit to an unreasonably high value, unless you want to watch a pathological interaction between Windows CE's weak memory manager (which frequently will close programs before hibernating them, judging from the results of experiments I've performed under 2.0) and Pocket Scheme's mark-and-sweep garbage collector (which will thrash the CE memory manager as it references every cell in the heap). For best results, keep the maximum heap limit within the amount of program memory on the device.

Tail recursion

Take advantage of tail recursion in your programs. The H/PC offers its applications very little stack space; a deeply recursive program that is not properly tail-recursive may possibly fall afoul of this limitation. Compare the following two recursive factorial implementations:

(define (f x) 
 (if (< x 2) 1 (* x (f (- x 1)))))

(define (f2 x) 
 (define (f2-int a i) 
  (if (> i x) a (f2-int (* i a) (+ i 1))))
 (f2-int 1 1))

The first, naive implementation, f, if given sufficiently great values of x, will overrun the callstack, as the interpreter saves all of its pending multiply-by-x frames on the stack. When the callstack overflows, Pocket Scheme must work to save and restore the stack, slowing execution considerably. The second implementation f2 recasts the expression tail-recursively, allowing the interpreter to optimize it into an iterative form that will run within a limited amount of stack space.

If you prefer not to use internal define clauses, consider the following two equivalent transformations.

(define (f3 x)
 (letrec ((iter
           (lambda (a i)
            (if (> i x) a (iter (* i a) (+ i 1))))) )
  (iter 1 1)))

(define (f4 x)
 (let loop ((a 1) (i 1))
  (if (> i x) a (loop (* i a) (+ i 1)))))

I/O and storage management

While the Scheme storage manager will close abandoned files when it finds them, it is often good practice to guard your open file streams with dynamic-wind or with-handlers, as shown in the following example:

(define (grep file exp)
  (define (grep-int stream re)
    (do ((l (read-string stream) (read-string stream)))
      ((eof-object? l) 'done)
      (if (regex-match re l) (write-string l))))
 (let ((cexp (regex exp 'no-values))
       (f (open-input-file file)) )
  (dynamic-wind
    (lambda () #t)
    (lambda () (grep-int f cexp))
    (lambda () (close-input-port f)) )))

In casual usage one can usually dispense with these guards, leaving the storage manager to do its job. (The Pocket Scheme storage manager enters a more aggressive mode when it sees open files in interactive usage.) Letting the storage manager handle closing the file leaves grep much prettier:

(define (grep file exp)
  (call-with-input-file file
    (lambda (in)
      (let ((rx (regex exp 'no-values)))
        (do ((l (read-string in) (read-string in)))
          ((eof-object? l) 'done)
          (if (regex-match rx l) (write-string l)))))))

The following example converts an 8-bit file from the Unix text file convention (where every line ends with ASCII 10) to the format expected by MS-DOS, Windows, Windows CE, etc. (where every lines ends with ASCII 13, then ASCII 10). You will find yourself using this function often if you download bodies of Scheme code from the Internet.

(define (lf2crlf infile outfile)
 (let ((i (open-input-file infile 'latin-1 'lf-newline))
       (o (open-output-file outfile 'latin-1 'crlf-newline)) )
  (let loop ((c (read-char i)))
   (if (eof-object? c)
    (begin (close-input-port i) (close-output-port o) #t)
    (begin
     (write-char c o)
     (loop (read-char i)) )))))

Note that this example lacks the dynamic-wind protection of the previous example; should the user interrupt execution of this function, Pocket Scheme would leave the files open until the next garbage collection (which would happen upon returning to the topmost REPL at the very latest). Note also that this example would misbehave on Unicode input, since it opens the input file explicitly as 8-bit characters, bypassing Pocket Scheme's automatic Unicode text recognition.

The following function generates a Unicode UCS-2 output file from an 8-bit or multibyte character input file.

(define (a2u infile outfile)
 (let ((i (open-input-file infile))
       (o (open-output-file outfile 'unicode)) )
  (let loop ((c (read-char i)))
   (if (eof-object? c)
    (begin (close-input-port i) (close-output-port o) #t)
    (begin (write-char c o) (loop (read-char i))) ))))

Foreign function interface

Pocket Scheme may call procedures that were not written in Scheme through its foreign function interface, or FFI. The procedure must be exported from a dynamic link library (DLL), have a calling sequence similar to a system call, and have a known, fixed parameter list.

A Scheme program acquires access to a foreign procedure through the procedure w32:foreign-procedure, which takes the name of a DLL, the name by which that DLL exports the procedure, and a list describing the calling sequence of the foreign code, and returns an applicable procedure object encapsulating access to the foreign code.

For example, Windows CE has a system call GetWindowTextLength that returns the number of characters of text data associated with a particular window. The system hosts an interface to that system function in its DLL coredll.dll. The DLL exports it under the name GetWindowTextLengthW. The function abides by the standard system calling sequence, returning 32 bits of integer data; it expects as a parameter a window handle (32 bits). Given this information, a Scheme program can make the call (w32:foreign-procedure "coredll.dll" "GetWindowTextLengthW" '(w32api dword handle)) to obtain a Scheme procedure object callable by Scheme that dispatches to this system call. Typically an application would make this particular w32:foreign-procedure call only once, binding the result to a symbol GetWindowTextLength or the like so that it could subsequently use expressions like (+ 1 (GetWindowTextLength some-window-handle)).

A calling sequence list must completely and correctly describe the calling sequence of the foreign code.

Arguments to foreign procedures may be of type Boolean, integer, null (the empty list), string, raw vector, or foreign. (The Pocket Scheme type foreign is 32 bits of magic, opaque to Scheme.) When Pocket Scheme applies a foreign procedure to a list of arguments, it examines each type in the list of parameters specified when creating the foreign procedure, using that type to convert the corresponding Scheme value in the argument list into a value for the underlying foreign function.

Depending on the return type specified, a foreign procedure will return a value of type Boolean, integer, void (the unspecified value), or foreign. Scheme code can coerce a received foreign value describing foreign data to a string value by calling foreign->string, to a raw vector value by calling foreign->raw-vector, or to an integer value by calling foreign->integer. Releasing allocated foreign data is the responsibility of client code, as Scheme cannot manage foreign memory.

Use the foreign data access procedures foreign->string and foreign->raw-vector with special care. Passing an improper argument to either of these procedures can crash the interpreter or corrupt data.

Win32 API calls

Pocket Scheme makes most Win32 calls through its foreign function interface, described above. For example, once a Scheme program has evaluated the definition

(define MessageBox (w32:foreign-procedure 
   "coredll.dll" "MessageBoxW" 
   '(w32api dword handle lpcwstr lpcwstr dword)))
it may subsequently evaluate
(MessageBox w32:*host-hwnd* 
  "This is a message.
It appears in its own window.
Tap the OK button to dismiss it."
  "Test Message" 0)
to display a message to the user using the MessageBox system call. (The somewhat baroque first parameter in this example retrieves the handle of the Pocket Scheme interpreter's display window to use as a parent window for the presented message window. The interpreter exports a number of such useful data through the globals w32:*host-hwnd*, w32:*host-hwnd-output*, and w32:*host-hinst*.)

See the sample file w32api.scm for additional examples of how to declare an API entry point. You will need to extend this set of declarations with additional imports for any additional system calls used by your application. Obtain the published function prototypes and constant definitions from Microsoft's SDK or equivalent documentation, e.g. that explaining how to use the Windows API from Visual Basic.

At present, these interfaces are best suited for writing console style applications. With effort, Scheme can power a graphical user interface, though the application author must grapple with the threading model of the window hosting the Scheme system, as well as window activation and sizing interactions with the host window. Desperate individuals may proceed to the experimental techniques and procedures described below; others, however, should wait for a forthcoming version of Pocket Scheme that will present a more rational window hosting model.

Windows

As the Pocket Scheme FFI cannot accommodate a generic C to Scheme callback, w32.dll exports special procedures to create windows and dialogs, and to associate windows and dialogs with Scheme procedures for window and dialog procedures. Each of these special procedures corresponds to a single Win32 system call, such as RegisterClass or CreateWindowEx. They are useless without additional API-level programming. Please see sdkgeneric.smd in the sample code, or the more recent sample plot.scm, for an example of using w32:register-class and w32:create-window.

The closure corresponding to a window procedure returns an integer value, typically 0, for every handled case. All unhandled cases must return the value of an explicit call to the Win32 DefWindowProc API. (Exception: a window created with a hint vector need not default to calling DefWindowProc in its window procedure, as that window will receive only those messages named in the vector.)

Here's an extremely simple window procedure. It presumes that the application has correctly declared DefWindowProc.

(lambda (hwnd msg wparam lparam)
  (case msg
    ((1) (display "Hello") (newline) 0)
    ((2) (display "Goodbye") (newline) 0)
    (else (DefWindowProc hwnd msg wparam lparam))))

The sample file w32message.scm defines a number of symbols for Win32 window messages, plus a macro that expedites using symbolic message definitions. Using these definitions, the previous example could read:

(lambda (hwnd msg wparam lparam)
  (symbolic-case msg
    ((WM_CREATE) (display "Hello") (newline) 0)
    ((WM_DESTROY) (display "Goodbye") (newline) 0)
    (else (DefWindowProc hwnd msg wparam lparam))))

For greater efficiency, the w32:create-window call could also supply a hint vector with the window procedure, constraining the application to call the window procedure only for those messages named in the hint vector. Given a hint vector of #( #.WM_CREATE #.WM_DESTROY ), the window procedure could then read:

(lambda (hwnd msg wparam lparam)
  (case msg
    ((#.WM_CREATE) (display "Hello") (newline) 0)
    ((#.WM_DESTROY) (display "Goodbye") (newline) 0)
    (else (error "The road never taken"))))

Dialogs

The new Pocket Scheme dialog creation procedures are quite difficult to use in their current state. w32:dialog-box-indirect and w32:create-dialog-indirect require the programmer to be comfortable manipulating DLGTEMPLATE and DLGITEMTEMPLATE C structures, which are not amenable to manipulation through the current simple w32:declare-foreign-struct macro. w32:dialog-box and w32:create-dialog require the programmer to have a dialog resource on a separate DLL. Like the window creation procedures, each of these new procedures corresponds to a single Win32 system call.

The closure corresponding to a dialog procedure resembles that for a window procedure, except that it returns a Boolean value instead of an integer, with all handled cases returning #t, and all unhandled cases returning #f. A dialog closure does not explicitly call DefWindowProc (or DefDialogProc) itself.

Structures

Many Win32 APIs require C structures as parameters. To manipulate those from Scheme code, use the macro w32:declare-foreign-struct. This macro takes the name of a foreign structure, the names of its fields, and the layout of the structure, generating a raw constructor for the structure, plus accessor and mutator procedures for each named field in the structure. For a structure 'S' with a field 'f', the macro will define a constructor make-S, an accessor S-f, a mutator S-f-set!, and the symbol S_sizeof.

As a simple example, consider the frequently used RECT structure, as seen in the APIs FillRect, GetClientRect, and others.

(w32:declare-foreign-struct RECT (left top right bottom) #(dword dword dword dword))
This declaration yields definitions for the constructor make-RECT, the accessors RECT-left, RECT-top, RECT-right, and RECT-bottom, and the mutators RECT-left-set!, RECT-top-set!, RECT-right-set!, and RECT-bottom-set!.

For a more sophisticated example, the C structure definition

typedef struct tagLOGFONT { // lf 
   LONG lfHeight; 
   LONG lfWidth; 
   LONG lfEscapement; 
   LONG lfOrientation; 
   LONG lfWeight; 
   BYTE lfItalic; 
   BYTE lfUnderline; 
   BYTE lfStrikeOut; 
   BYTE lfCharSet; 
   BYTE lfOutPrecision; 
   BYTE lfClipPrecision; 
   BYTE lfQuality; 
   BYTE lfPitchAndFamily; 
   TCHAR lfFaceName[LF_FACESIZE]; 
} LOGFONT;
(from the SDK header file wingdi.h) becomes the Scheme macro invocation
(w32:declare-foreign-struct LOGFONT 
 (height width escapement orientation weight
  italic underline strikeout charset 
  outprecision clipprecision quality pitchandfamily
  facename)
 #(dword dword dword dword dword
   byte byte byte byte 
   byte byte byte byte
   (wchar 32)) )

Note that the current version of this macro does not generate accessors for C structure bitfields. Read the file cm17a.scm to see one way to work around this limitation, setting the bitfields within a DCB. The macro also cannot presently generate accessors for structures embedded within structures. The file plot.scm demonstrates a workaround, accessing the RECT embedded within a SIPINFO.

Caveats

The FFI is a very low-level tool. Erroneous programs using the FFI have the power to crash the interpreter with an illegal memory reference, leak memory or device context handles, or even hang the device (necessitating a reset) by creating the wrong kind of window in the wrong place. All manner of bugs formerly available only to C programmers can now be yours. While Pocket Scheme will attempt to recover from illegal memory references, you should still exercise caution.

Numeric limitations

Pocket Scheme represents all inexact numbers as floating-point quantities, and reads any numeric literal containing a decimal point as an inexact number. Hence the following naive sequence will never terminate, due to an accumulation of floating-point errors:

(do ((i -5 (+ i 0.1))) ((= i 5) (newline)) (display i) (display #\space))

To avoid this, either make the termination condition use >= instead of =, or else work in exact quantities by using the correct numeric literal syntax for an exact number, #e0.1 or 1/10.


Other destinations

Pocket Scheme
Back to the Pocket Scheme page.
Site Map
Find your way around this site

Last modified: Wed Aug 17 21:58:19 Pacific Standard Time 2005

Ben Goetter (contact information)

Copyright 1998-2005, Ben Goetter. All rights reserved.