[racket] An easy route to define-datatype in BSL?

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Mon Nov 5 06:43:04 EST 2012

At Sun, 04 Nov 2012 16:59:26 -0500, Prabhakar Ragde wrote:
> I'm debating trying to use something like define-datatype, as used in 
> EOPL and PLAI (and available in the Racket languages that support those 
> books) early in my first-year undergraduate class. (Opinions on the 
> wisdom of this, or on my sanity in general, by direct e-mail, please.)
> In a teaching-language program, just using `require' to include 
> define-datatype and type-case or 'cases' from plai/datatype.rkt or 
> eopl/datatype.rkt appears to work so long as the teaching language is 
> ISL or higher. In BSL or BSL+, I seem to be running into a thicket of 
> rules designed to protect students. I'm using as my exploratory example 
> the trivial arithmetic expression evaluator from the beginning of PLAI 
> (2007 edition). Using plai/datatype.rkt generates an error because a use 
> of define-datatype involves function names in expression positions (as 
> contracts for field names). If I wrap these in applications of 
> first-order->higher-order from lang/prim, which seems to be designed for 
> this task, I get an error the first time I try to use a variant 
> constructor (in the PLAI example, in 'parse'). The constructors seem to 
> be defined, but also seem to be regarded as variables instead of 
> functions. This is also what happens with eopl/datatype.rkt (the other 
> error does not seem to arise).
> Do I have a hope of pulling this off, and if so, how? Many thanks. --PR

Here's a first cut at a module that you can import into BSL programs.

The least obvious part is defining a new `define-type' and `type-case'
that invents hidden names for the variants and them maps between them
while expanding to the original forms.

#lang racket/base
(require plai/datatype
         (prefix-in beginner: lang/htdp-beginner)
         (for-syntax racket/base
(provide (rename-out
          [my-define-type define-type]
          [my-type-case type-case]))

(define-for-syntax variants (make-free-identifier-mapping))
(define-for-syntax (record-variants! vars x-vars)
  (for ([var (in-list vars)]
        [x-var (in-list x-vars)])
    (free-identifier-mapping-put! variants var x-var)))

(define-syntax (my-define-type stx)
  (syntax-case stx ()
    [(_ ty [var (fld ctc) ...] ...)
     (with-syntax ([(x-var ...)
                    (map (lambda (s)
                             (symbol->string (syntax-e s)))
                         (syntax->list #'(var ...)))])
       (define (adjust new-stx)
         ;; `define-type' seems to use the context of the
         ;; form for `ty?', so copy over old context
         (datum->syntax stx (syntax-e new-stx) stx stx))
              #'(define-type ty
                  [x-var (fld (first-order->higher-order ctc)) ...]
           (beginner:define (var fld ...)
                            (x-var fld ...))
            (record-variants! (list #'var ...) (list #'x-var ...)))))]))

(define-syntax (my-type-case stx)
  (syntax-case stx ()
    [(_ Ty e [var (id ...) rhs] ...)
     (with-syntax ([(x-var ...)
                    (map (lambda (var)
                           (or (free-identifier-mapping-get
                                (lambda ()
                                   "not a variant name"
                         (syntax->list #'(var ...)))])
       #`(type-case Ty e [x-var (id ...) rhs] ...))]))

Posted on the users mailing list.