It Seemed Like a Good Idea at the Time Coding, Mostly

21Mar/092

Fixing Broken Macros with Eval and Quasiquote

Recently, I found myself needing to deal with a "convenience" macro, which quoted several of its arguments for me before passing them along to the real function.  Unfortunately, only the macro was exported from the library, and I was unable to access the base function.

(define-syntax convenient-function
  (syntax-rules ()
    ((_ arg1 arg2) (much-harder-function 'arg1 'arg2))))

How useful.  To save me a handful of quotes, I lose the ability to programmatically generate my arguments.

As I was loath to reimplement the entire library just to regain that ability, I looked for another solution.  Thankfully, I found one.

(define (much-harder-function arg1 arg2)
  (eval `(convenient-function ,arg1 ,arg2)
        (environment '(convenient-lib))))

As far as I can tell, this circumvents the macro's quoting features entirely to allow me to pass in a dynamically generated symbol.  While it won't work with functions which modify the environment, it worked well in my case, and allowed me to move on to more interesting code.

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)
Tagged as: , , 2 Comments
14Mar/091

SRFI-0 for Detecting Scheme Implementation

I found out earlier today that SRFI-0 can be used to determine the host Scheme in some cases.  Here is the result of my testing, in case someone finds it useful:

No SRFI-0 by default:

  • mzscheme
  • ikarus
  • scheme48

Name exported for cond-expand:

Scheme Name     SRFI-0 Feature Name
CHICKEN chicken
Guile guile
Bigloo bigloo
Gambit gambit
MIT Scheme mit
Gauche gauche
Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)
Tagged as: , 1 Comment
14Mar/091

Runtime Scheme Detection

I really want my code to be "write once, run anywhere", at least as far as Schemes go.  R6RS library features make strides toward that goal.  Only, there's not many R6RS Schemes.  Since there are a whole lot of R5RS ones, I'd like to include R5RS in the portability fun.

In many cases, enough functionality appears in all major implementations to make some major steps toward portability.  So a while back, I decided to get a few things out of the way (you know, processes, FFI, things like that).

To make simple, portable libraries, we need to find out some details about the Scheme implementation the code is running under.  Unfortunately, there is no standard way to do this.  We could just call a function and see if it worked, but then we throw an error, and there's no standard way to trap those in R5RS.  And even if a function of the given name existed, we still don't know what arguments it takes, because that differs too.

So we're left with some sort of indirect detection method,  guessing the implementation based on a bunch of unrelated tests.

So I gathered a list of as many nonstandardized/undefined behaviours as possible (mainly from this table, and grepping the R5RS spec for the word "undefined").  Then I filtered those differences down to a bunch of tests that won't error out.  Then I took the tests that gave meaningful results, and because I had enough of those to be picky, I took only tests which fit on a single line.

The resulting set of 20 tests generates a sort of "signature" -- a list of twenty boolean values that identifies the host Scheme.

Currently the code works on:

  • MzScheme
  • CHICKEN
  • Guile
  • Bigloo
  • Gambit
  • Ikarus
  • Scheme48
  • MIT Scheme
  • Gauche

This is actually a list of all the implementations I have installed right now, and other implementations can be supported just by adding another line to the signatures list.

On Scheme implementations which provide a compiled and an interpreted mode, only interpreted has been tested (I don't really know how to use a compiled Scheme, so I didn't).  Some Schemes have different behaviour interpreted and compiled, so caution should be used there.

Enough talk, here's the code:

;;;
;;; DETECT
;;;   A set of functions to allow an interpreted Scheme
;;;    program to determine the implementation it is
;;;    running under.
;;;

;;
;; DETECT:SIGNATURE
;;   Assemble a signature for the current
;;   Scheme implementation.
;;
(define (detect:signature)
  (list
    ;; AXCH: exact-sqrt
    (exact? (sqrt 4))
    ;; AXCH: exact-times-zero
    (exact? (* 0 3.1))
    ;; AXCH: exact-div-zero
    (exact? (/ 0 4.7))
    ;; AXCH: exact-rationals
    (exact? (/ 1 3))
    ;; AXCH: case-sensitive
    (eq? 'a 'A)
    ;; AXCH: promises-are-thunks
    (procedure? (delay 3))
    ;; Do strings made from numbers less than 1 omit the 0?
    (string=? ".5" (number->string 0.5))
    ;; AXCH: literal-rationals
    (number? (string->number "1/2"))
    ;; AXCH: literal-complexes
    (number? (string->number "1+i"))
    ;; Is the empty string eqv to itself?
    (eqv? "" "")
    ;; How about the empty vector?
    (eqv? '#() '#())
    ;; A non-empty string?
    (eqv? "a" "a")
    ;; Does SET! have a constant return value?
    (let ((x 0)) (eqv? (set! x 1) (set! x 'asd)))
    ;; Is it equal to other undefined things?
    (eqv? (for-each (lambda (x) #t) '(0 1 2)) (let ((x 123)) (set! x 321)))
    ;; Are negative and positive inexact zero the same?
    (eq? +0.0 -0.0)
    (eqv? +0.0 -0.0)
    (equal? +0.0 -0.0)
    ;; Is the default vector filled with zeroes?
    (equal? (make-vector 5) '#(0 0 0 0 0))
    ;; Is the default vector filled with falses?
    (equal? (make-vector 5) '#(#f #f #f #f #f))
    ;; Vector-fill returns a vector?
    (vector? (vector-fill! (make-vector 1) 0))
    ))

;;
;; DETECT:KNOWN-SIGNATURES
;;   A precalculated list of signatures for all supported
;;    Scheme implementations.
;;
(define detect:known-signatures
'((mzscheme   (#t #t #t #t #f #f #f #t #t #f #f #f #t #t #f #f #f #t #f #t))
  (chicken    (#f #f #f #f #f #f #f #t #f #f #f #f #t #t #f #t #t #f #f #f))
  (guile      (#f #t #f #t #f #f #f #t #t #t #f #f #t #t #f #f #t #f #f #f))
  (bigloo     (#f #f #f #f #f #t #f #f #f #f #f #f #t #t #f #t #t #f #f #f))
  (gambit     (#t #t #t #t #f #f #t #t #t #f #f #f #t #t #f #f #f #t #f #f))
  (ikarus     (#f #f #f #t #f #t #f #t #f #f #f #f #t #f #f #t #t #t #f #f))
  (scheme48   (#f #f #f #t #t #t #f #t #t #t #t #t #t #t #t #t #t #f #f #f))
  (mit-scheme (#t #t #t #t #t #f #t #t #t #f #t #f #f #f #f #t #t #f #t #f))
  (gauche     (#f #f #f #t #f #f #f #t #t #f #f #f #f #f #f #t #t #f #f #f))))

;; DETECT:MATCH-SIGNATURE
;;   Determine the name of the current Scheme implementation
;;    by checking the signature returned by DETECT:SIGNATURE
;;    against a table of known signatures.
(define (detect:match-signature)
  (let ((signature (detect:signature)))
    ; Loop over the DETECT:KNOWN-SIGNATURES list
    (let test ((siglist detect:known-signatures))
      (if (equal? '() siglist)
        ; Return 'UNKNOWN if we're stumped
        'unknown
        (let ((testsig (car siglist)))
          (if (equal? (cadr testsig) signature)
            (car testsig)
            (test (cdr siglist))))))))

;;
;; DETECT:NAME
;; Memoized form of DETECT:MATCH-SIGNATURE
;;
(define detect:name
  (let ((memo #f))
    (lambda ()
      (and (not memo)
           (set! memo (detect:match-signature)))
      memo)))

Obviously, only the memoized DETECT:NAME is meant for general use, since a program generally won't the see the host changing as it runs, and repeatedly calculating the name would be inefficient.  I don't actually have much experience with Scheme, so the DETECT:MATCH-SIGNATURE function could probably be written more efficiently, but it works.

Anyway, I hope that someone finds this to be useful.  I have used it to write the beginnings of a portable process library.  More on that after I get around to polishing it up a bit more.

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)