Algorithms for functional programming: the blog

News and behind-the-scenes details on AFP: the book
Posts tagged as ikarus scheme

Another bug in Ikarus Scheme?

2011-09-07 by stone

I've run into another problem that seems to be a bug in Ikarus Scheme, this one in the processing of syntax definitions. This is supposed to be one of Ikarus's strong points -- the implementer published a paper on the portable syntax-case system that he developed for Ikarus -- so I was surprised to encounter this difficulty.

I'm using syntax-case to automate the implementation of "tuple" data types -- fixed-length lists in which each position is supposed to hold values of a specified type. The idea is that I want to be able to write something like

(define-tuple star (name string? string=?)
                   (magnitude number? =)
                   (distance number? =)
                   (spectral-class symbol? symbol=?))

and have it expand into a sequence of definitions: a constructor, a selector for each field, a "disaggregator" that returns the components as separate values, a classification predicate, and an equality predicate, thus:

(begin
  (define (make-star name magnitude distance spectral-class)
    (list name magnitude distance spectral-class))
  (define (star-name star)
    (list-ref star 0))
  (define (star-magnitude star)
    (list-ref star 1))
  (define (star-distance star)
    (list-ref star 2))
  (define (star-spectral-class star)
    (list-ref star 3))
  (define (destar star)
    (apply values star))
  (define (star? something)
    (and (list? something)
         (= (length something) 4)
         (string? (list-ref star 0))
         (number? (list-ref star 1))
         (number? (list-ref star 2))
         (symbol? (list-ref star 3))))
  (define (star=? left right)
    (and (string=? (list-ref left 0) (list-ref right 0))
         (= (list-ref left 1) (list-ref right 1))
         (= (list-ref left 2) (list-ref right 2))
         (symbol=? (list-ref left 3) (list-ref right 3)))))
Here's my definition of the define-tuple syntax:
(define-syntax define-tuple
  (letrec ((as-string
            (lambda (x)
              (cond ((identifier? x)
                     (as-string (syntax->datum x)))
                    ((symbol? x)
                     (symbol->string x))
                    ((string? x) x))))
           (construct-id
            (lambda (context . parts)
              (datum->syntax context
                             (string->symbol
                              (apply string-append
                                     (map as-string parts))))))
           (iota
            (lambda (n)
              (let loop ((count n) (so-far '()))
                (if (zero? count)
                    so-far
                    (let ((next (- count 1)))
                      (loop next (cons next so-far))))))))
    (lambda (input-form)
      (syntax-case input-form ()
        ((_ type-name (field-name classification equality) ...)
         (with-syntax ((constructor-name
                        (construct-id #'type-name "make-" #'type-name))
                       ((selector-name ...)
                        (map (lambda (fn)
                               (construct-id #'type-name
                                             #'type-name
                                             "-"
                                             fn))
                             #'(field-name ...)))
                       (field-count
                        (datum->syntax #'type-name
                                       (length #'(field-name ...))))
                       ((field-index ...)
                        (iota (length #'(field-name ...))))
                       (disaggregator-name
                        (construct-id #'type-name "de" #'type-name))
                       (classification-predicate-name
                        (construct-id #'type-name #'type-name "?"))
                       (equality-predicate-name
                        (construct-id #'type-name #'type-name "=?")))
           #'(begin
               (define (constructor-name field-name ...)
                 (list field-name ...))
               (define (selector-name type-name)
                 (list-ref type-name field-index)) ...
               (define (disaggregator-name type-name)
                 (apply values type-name))
               (define (classification-predicate-name something)
                 (and (list? something)
                      (= (length something) field-count)
                      (classification
                       (list-ref something field-index)) ...))
               (define (equality-predicate-name left right)
                 (and (equality (list-ref left field-index)
                                (list-ref right field-index))
                      ...)))))))))

Ikarus accepts this syntax definition without complaining, but hangs when it encounters any use of define-tuple, as if it can't complete the expansion process. Neither PLT Racket nor Larceny has this problem.

Multiplicative-inverse bug in Ikarus Scheme

2011-08-22 by stone

In testing the examples from chapter 1 of AFP, I turned up a bug in the Ikarus Scheme processor (version 0.0.3). One of my examples computes the multiplicative inverse of -3/8, using the expression (/ -3/8). The test code, then, was essentially

(= (/ -3/8) -8/3)

The test failed -- Ikarus Scheme returns #f as the value of the equality test. I tried a few interactions to try to get some idea of what was going on, but I found the results mystifying:

> (/ -3/8)
8/-3

This isn't exactly wrong, but the form of the number is not what I expecteed, since 8/-3 is not a valid R6RS literal. Ikarus Scheme accepts such literals as input, but appears to normalize them:

> 8/-3 
-8/3

It looks like the one-argument / procedure is internally generating a rational value with a negative denominator and not normalizing the result:

> (rational? (/ -3/8))
#t
> (numerator (/ -3/8))
8
> (denominator (/ -3/8))
-3

Then the = predicate gets the wrong answer because it somehow relies the precondition that denominators are positive. It's not that the = predicate can't deal with signed values:

> (= -8/3 8/-3)
#t
> (= -8/-3 16/6)
#t

Other procedures deal correctly with the negative-denominator rational:

> (< 8/-3 (/ -3/8))
#f
> (> 8/-3 (/ -3/8))
#f
> (<= 8/-3 (/ -3/8))
#t
> (>= 8/-3 (/ -3/8))
#t
> (- 8/-3 (/ -3/8))
0

The two-argument form of the / procedure gives the correct answer:

> (/ 1 -3/8)
-8/3
> (= (/ -3/8) (/ 1 -3/8))
#f
> (- (/ -3/8) (/ 1 -3/8))
0

I looked over the list of bugs fixed in version 0.0.4 and didn't find this one, so I filed a bug report on the LaunchPad site for Ikarus Scheme.

Testing equality for exact rationals seems pretty straightforward -- you multiply the numerator of each one by the denominator of the other and determine whether the resulting integers are equal. But this should work even without the precondition that denominators be positive. I wonder what alternative approach the author of Ikarus Scheme used -- I must be missing something.

Running a top-level R6RS program under Ikarus Scheme

2011-08-16 by stone

With the setup described in my previous post on Larceny, I was able to run the same top-level program under Ikarus Scheme with the shell command

  ikarus --r6rs-script top-level-greeter.ss

Ikarus Scheme presupposes that the names of the libraries are relative to the current working directory.

Current R6RS implementations

2011-08-16 by stone

In developing and testing the algorithm implementations in Algorithms for functional programming, I propose to use Scheme language processors that implement the R6RS standard completely and correctly and are available as Debian packages, ideally packages that the Debian Project itself includes in the free stable distribution.

The list of R6RS implementations at http://www.r6rs.org/implementations.html identifies eight of them: Chez Scheme, Guile, Ikarus Scheme, IronScheme, Larceny, Mosh Scheme, PLT Racket, and Ypsilon.

A binary-only version of Chez Scheme (without the compiler) is available under an unfree license from the developer. The current version is 8.3.

Chez Scheme download site ... Chez Scheme version 8 user's guide ... The Scheme programming language, fourth edition

Guile is available as a Debian package (guile-1.8), but the version that Debian provides is older and does not implement R6RS. A newer version is available from the GNU Project as a tarball.

GNU Guile download site ... Guile reference manual (PDF version)

Ikarus Scheme is available as a Debian package (ikarus), but it is not a complete implementation of R6RS Scheme, and no one appears to be maintaining it. The developer's site (http://ikarus-scheme.org/) is currently out of service.

Ikarus Scheme user's guide

IronScheme is based on the Microsoft Dynamic Language Runtime and so cannot be expected to run in a GNU environment.

Larceny is available as a prebuilt binary for the Intel 32-bit architecture. The developer also provides source code, under the Lesser General Public License and an alternative license that appears to be unique to Larceny.

Larceny download site ... Larceny User Manual (PDF version)

Mosh Scheme is available as a tarball.

Mosh Scheme download site ... Mosh Scheme user manual

The Debian Project packages and distributes PLT Racket (version 5.1.1) for version squeeze in its squeeze-backports/main section. The package names are racket and racket-doc. If you already have the PLT Scheme packages installed, the Debian package manager will require you to remove them as you install PLT Racket.

Racket documentation

Ypsilon is available as a tarball (version 0.9.6.update3). There appears to be no useful documentation for it.

Ypsilon download site

I already had Ikarus Scheme on the desktop that I'll be working on this semester, and I installed the Debian packages for PLT Racket and the binaries for Larceny. For compatibility testing, I plan eventually to install Guile and Mosh Scheme as well.

Although it is possible to install Larceny in user space, I decided that it would be preferable to have set it up in /opt. As root on eunoia, I downloaded the tarball into /tmp and unpacked it into a new directory in /opt:

  cd /tmp
  /usr/bin/wget http://www.larcenists.org/LarcenyReleases/larceny-0.97-bin-native-ia32-linux86.tar.gz
  cd /opt
  /bin/tar xvzf /tmp/larceny-0.97-bin-native-ia32-linux86.tar.gz 

I then added the lines

  if [ -d /opt/larceny-0.97-bin-native-ia32-linux86 ]
  then
    PATH=$PATH:/opt/larceny-0.97-bin-native-ia32-linux86
  fi

to add Larceny's directory to the shell's search path.