[plt-dev] some Racket proposals & implementation

From: Jay McCarthy (jay.mccarthy at gmail.com)
Date: Sat Apr 3 00:21:13 EDT 2010

While grinding in FF13 tonight, I implemented most of this.

I've attached three files. Put them in the same directory and you can
run sstruct-tests.ss

I allow default value expressions, keyword constructors, overriding
names of accessors, mutators, constructors, and predicates. I support
parent structs and match expanders.

There's a bunch of places where more stuff could be allowed that I
don't do yet. And I still have 8 test cases failing for those. Most of
these and additional features should be easy to support.

A few annoyances:

First, I have to specify the parent of a struct with struct:id rather
than 'id'. The problem is that I'd like 'id' to have three jobs:

1. match expander
2. constructor
3. struct type info

Both 1 and 3 are static values so they'll conflict with each other.

Second, syntax/parse doesn't allow ~or patterns to have no
subpatterns. I don't see why. I hack it in the match expander.

Third, syntax/parse doesn't have a good way of rewriting a pattern for
keywords in any order, so I have to separate them out first. This
makes the match expander code really ugly.

Fourth, the static sstruct description includes the syntax of the
constructor formals, which may contain the default value expression.
That's a little weird, but kind of cool also.

I consider point 1 a bit of a game breaker, unless we're willing to
break compatibility even more. (I personally dislike the
(define-struct (child parent) ...) stx and would prefer #:parent
parent.) If anyone has ideas on that, I would be much obliged.

I plan on continuing to do stuff to this if there seems to be interest.

Jay

On Fri, Apr 2, 2010 at 5:35 PM, Matthew Flatt <mflatt at cs.utah.edu> wrote:
> Support for keyword and optional arguments would be good. I don't think
> I'd extend the auto-field description, but instead have something like
>
>  (define-struct a (w [x (add1 w)] #:y y #:z [z (add1 y)]))
>
> be analogous to defining the constructor as a function
>
>  (lambda (w [x (add1 w)] #:y y #:z [z (add1 y)]) ...)
>
> but with the same selector functions as
>
>  (define-struct a (w x y z))
>
> As another example,
>
>  (define-struct a (w
>                   [x (add1 w)]
>                   #:y [y #:mutable]
>                   #:z [z (add1 y) #:mutable]))
>
> would have a constructor with the same signature as above, but with
> mutable `y' and `z' fields.
>
> Some challenges I see:
>
>  * reporting enough static information for `match' to support the right
>   syntax for patterns with `a';
>
>  * subtyping of a type whose constructor uses keyword arguments;
>
>  * reflective access to constructors (via `struct-type-make-constructor',
>   for example); and
>
>  * printing values when the constructor has keyword arguments.
>
> I think all of these can be addressed, but it's a lot of work (so if
> it depends on me, I'm inclined to put it off a while longer).
>
> At Fri, 2 Apr 2010 16:11:10 -0600, Doug Williams wrote:
>> Would it be possible to combine the structure constructor name with a means
>> of providing default structure values on a per slot basis? I had always
>> thought it would be nice if the auto-v argument to make-struct-type took an
>> old-style lambda list (i.e., an improper list or, in the degenerate case, an
>> atom) to allow specification of slot-specific automatic (default) values -
>> with the cdr of an improper list (or the atom in the degenerate case) being
>> used for the remaining elements. Then, define-struct could support a
>> #:default field option for each slot. Just a thought.
>>
>> On Fri, Apr 2, 2010 at 3:30 PM, Matthew Flatt <mflatt at cs.utah.edu> wrote:
>>
>> > Version 4.2.5.5 in the SVN trunk includes experimental features to
>> > support the following proposed Racket features. You can try the
>> > proposals with `#lang racket' in MzScheme.
>> >
>> > Structure Constructor Names
>> > ---------------------------
>> >
>> > Proposal: The default constructor name bound by `define-struct' in
>> > Racket should be the same as the type name, instead of having a `make-'
>> > prefix.
>> >
>> > Example:
>> >
>> >     > (define-struct a (x y))
>> >     > a
>> >     #<procedure:a>
>> >     > (a 1 2)
>> >     #<a>
>> >
>> > To help support this potential feature, the `define-struct' form of
>> > `scheme/base' now accepts a `#:constructor-name' argument to give the
>> > constructor a name other than the one prefixed with `make-'. In
>> > particular, the constructor name can be the same as the type name:
>> >
>> >     > (define-struct a (x y))
>> >     > make-a
>> >     #<procedure:make-a>
>> >     > (define-struct a (x y) #:constructor-name a)
>> >     > a
>> >     #<procedure:a>
>> >     > (a 1 2)
>> >     #<a>
>> >
>> > A natural (and generally backward-compatible) to change to `match'
>> > would be to treat structure-type names as pattern constructors, so that
>> >
>> >     (match (a 1 2)
>> >       [(a x y) x])
>> >
>> > would produce 1.
>> >
>> >
>> > Semi-quasiquote Printing
>> > ------------------------
>> >
>> > Proposal: Use quasiquote printing as Racket's default printing mode,
>> > but only for transparent values.
>> >
>> > Functional programmers long ago figured out that it's better to print a
>> > value in the same way as an expression that produces the value.
>> > Printing with `quasiquote', meanwhile, mostly preserves the Lisp
>> > tradition of printing values that represent expressions as the
>> > expressions that they represent.
>> >
>> > Some values, however, cannot be printed easily as expressions that
>> > produce the same value. For example, In DrScheme with quasiquote
>> > printing,
>> >
>> >  (list 1 (let ([f (lambda (x) x)]) f))
>> >
>> > prints as
>> >
>> >  `(1 ,(lambda (a1) ...))
>> >
>> > The printer cannot actually print a function, so it has to invent a
>> > `lambda' expression that approximates the value. The problem is worse
>> > with objects, classes, and other opaque types. Expressions with graphs
>> > print as a `shared' expression.
>> >
>> > Other implementations of functional languages punt on opaque values.
>> > Here's an example in OCaml, which prints functions as just `<fun>':
>> >
>> >  # Some 10;;
>> >  - : int option = Some 10
>> >  # sqrt;;
>> >  - : float -> float = <fun>
>> >  # [sqrt;sqrt];;
>> >  - : (float -> float) list = [<fun>; <fun>]
>> >
>> > This seems like the right compromise for Racket. For example,
>> >
>> >  (list 1 (let ([f (lambda (x) x)]) f))
>> >
>> > could print as
>> >
>> >  `(1 #<procedure:f>)
>> >
>> > (Note that there's no need for an unquote when printing a value as a
>> > non-expression. Non-S-expression forms are "self-unquoting".)
>> >
>> > Transparent (or prtially transparent) structures can print with
>> > constructors, while opaque structures can print as non-S-expressions:
>> >
>> >   > (define-struct a (x y))
>> >   > (list 1 (a 2 3))
>> >   `(1 #<a>)
>> >   > (define-struct a (x y) #:transparent)
>> >   > (list 1 (a 2 3))
>> >   `(1 ,(a 2 3))
>> >
>> > Instances of prefab structure types, meanwhile, should stick to
>> > quasiquoting:
>> >
>> >   > (define-struct b (x y) #:prefab)
>> >   > (list 1 (b (a 2 3) 'x))
>> >   `(1 #s(b ,(a 2 3) x))
>> >
>> > Graphs can still use the compact #n= notation:
>> >
>> >   > (read (open-input-string "#0=(1 . #0#)"))
>> >   `#0=(1 . #0#)
>> >
>> > Unlike DrScheme's quasiquote printing, semi-quasiquote printing is
>> > easily implemented by parameterizing our existing printer(s).
>> >
>> > A new `print-as-quasiquote' parameter directs `print' and
>> > `pretty-print' to use semi-quasiquote style. (The parameter does not
>> > affect `write'.)
>> >
>> >  Welcome to MzScheme v4.2.5.5 [3m], Copyright (c) 2004-2010 PLT Scheme Inc.
>> >  > 'x
>> >  x
>> >  > (print-as-quasiquote #t)
>> >  > 'x
>> >  'x
>> >  > (list 1 2 3)
>> >  `(1 2 3)
>> >  > sqrt
>> >  #<procedure:sqrt>
>> >  > (list 1 sqrt)
>> >  `(1 #<procedure:sqrt>)
>> >
>> > The `port-print-handler' and `prop:write' protocols have been changed
>> > (in a mostly backward-compatible way) to make semi-quasiquote printing
>> > extensible.
>> >
>> >
>> > Language-Specific Run-Time Configuration
>> > ----------------------------------------
>> >
>> > Proposal: The main language of a program should determine a run-time
>> > configuration, including the style for printing values.
>> >
>> > Assuming the changes above, we'd want
>> >
>> >  #lang scheme
>> >  (define-struct a (x y) #:transparent)
>> >  (list (make-a 1 2))
>> >
>> > to produce
>> >
>> >  (#(struct:a 1 2))
>> >
>> > while
>> >
>> >  #lang racket
>> >  (define-struct a (x y) #:transparent)
>> >  (list (a 1 2))
>> >
>> > should produce
>> >
>> >  `(,(a 1 2))
>> >
>> > Along the same lines, we'd want
>> >
>> >  #lang scheme
>> >  (define-struct a (x y) #:transparent)
>> >  (+ 'x (list (make-a 1 2)))
>> >
>> > to produce the error message
>> >
>> >  +: expects type <number> as 1st argument, given: x; other arguments
>> >  were: (#(struct:a 1 2))
>> >
>> > while
>> >
>> >  #lang racket
>> >  (define-struct a (x y) #:transparent)
>> >  (+ 'x (list (a 1 2)))
>> >
>> > should produce the error message
>> >
>> >  +: expects type <number> as 1st argument, given: 'x; other arguments
>> >  were: `(,(a 1 2))
>> >
>> > The different `define-struct's are easily support through different
>> > bindings imported by `scheme' and `racket'. Similarly, for printing
>> > top-level results in a module, you might imagine that `scheme' and
>> > `racket' use different printing functions. The different error formats,
>> > however, are not so easily controlled through bindings.
>> >
>> > Setting `print-as-quasiquote' to #t is enough to get the Racket-style
>> > error format, but having `#lang racket' inject `(print-as-quasiquote
>> > #t)' in the module top-level would not work well when modules from
>> > different languages are mixed together. For example, if a program
>> > imports both
>> >
>> >  ;; s.ss:
>> >  #lang scheme
>> >  (define (s-bad v) (error 's-bad "~e" v))
>> >  (provide s-bad)
>> >
>> > and
>> >
>> >  ;; r.rkt
>> >  #lang racket
>> >  (define (r-bad v) (error 'r-bad "~e" v))
>> >  (provide r-bad)
>> >
>> > the way an error message is printed by `s-bad' and `r-bad' shouldn't
>> > depend on the order that the modules are instantiated.
>> >
>> > To accommodate run-time configuration of the environment, such as
>> > setting the way that values are printed, `mzscheme' now treats the main
>> > module of a program specially. It extracts information about the
>> > module's language --- specifically, whether the language declares a
>> > run-time configuration action. If so, `mzscheme' runs the
>> > language-configuration action before it instantiates the module.
>> >
>> > As a result, when you put either version of the code above in "ex.ss",
>> > then `mzscheme ex.ss' produces the right error message.
>> >
>> > Here's how it works in more detail for the case of `#lang racket':
>> >
>> >  * The `racket' module reader has implemented in `racket/lang/reader'
>> >   associates a 'module-language property with `module' form that it
>> >   produces from "ex.ss". The 'module-language property essentially
>> >   points back to `racket/lang/reader'.
>> >
>> >  * The macro expander and bytecode compiler preserves the
>> >   'module-language information so that it's available through
>> >   `module-compiled-language-info' (from the unevaluated bytecode)
>> >   and/or `module->language-info' (from the evaluated module
>> >   declaration).
>> >
>> >  * When the `mzscheme' executable is given a module to run, it uses
>> >   `module->language-info' to get the module's language information
>> >   before `require'ing the module. The `module->language-info' loads
>> >   "ex.ss" (from source or bytecode) and extracts language info from
>> >   the declared module.
>> >
>> >   The language info on the declaration of the module from "ex.ss"
>> >   points back to the `get-info' export of `racket/lang/reader'. The
>> >   `mzscheme' executable calls that function with the
>> >   'configure-runtime key.
>> >
>> >  * The `get-info' function of `racket/lang/reader' recognizes the
>> >   'configure-runtime key and reports back the `configure' function
>> >   provided by `racket/private/runtime'.
>> >
>> >   [Why doesn't `get-info' just call `configure' directly? See below
>> >    on creating executables.]
>> >
>> >  * The `mzscheme' executable calls the `configure' function of
>> >   `racket/private/runtime' calls it. The `configure' function simply
>> >   sets the `print-as-quasiquote' parameter to #t.
>> >
>> >  * Having finished running the language's configuration action, the
>> >   `mzscheme' executable `require's the "ex.ss" module to instantiate
>> >   it. (Although `module->language-info' has already loaded the module,
>> >   `module->language-info' doesn't instantiate the module.)
>> >
>> >   Instantiating the module runs the expressions in its body,
>> >   triggering the `+' error. The error message uses the right style for
>> >   printing values because the `print-as-quasiquote' parameter was set
>> >   to #t by `configure'.
>> >
>> > If you run `mzc --exe ex ex.ss', the generated executable prints the
>> > right error message, too. That's because `mzc' extracts the main
>> > module's language information in the same way as `mzscheme'. Based on
>> > the result for 'confgure-runtime for the module's language, `mzc'
>> > embeds the `racket/private/runtime' module in the generated executable
>> > (and that's why `get-info' doesn't call `configure' itself). The
>> > generated executable includes a start-up action that calls `configure'
>> > before running the main module.
>> >
>> >
>> > DrScheme should similarly extract language information and call
>> > `configure' before running the module. It may be that a single
>> > side-affecting `configure' function isn't the right interface for
>> > DrScheme, and so experiments with DrScheme may lead to a different
>> > protocol for `mzscheme' and `mzc'.
>> >
>> >
>> > When `mzscheme' is run in interactive module, the initialization
>> > module's language is used to initialize the run-time configuration. The
>> > `racket', `racket/base' and `racket/init' modules are implemented in
>> > Racket, so
>> >
>> >   mzscheme -I racket/init
>> >
>> > gives you a REPL like `racket' could give you (when it exists).
>> >
>> > _________________________________________________
>> >  For list-related administrative tasks:
>> >  http://list.cs.brown.edu/mailman/listinfo/plt-dev
>> >
> _________________________________________________
>  For list-related administrative tasks:
>  http://list.cs.brown.edu/mailman/listinfo/plt-dev
>



-- 
Jay McCarthy <jay at cs.byu.edu>
Assistant Professor / Brigham Young University
http://teammccarthy.org/jay

"The glory of God is Intelligence" - D&C 93
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sstruct-syn.ss
Type: application/octet-stream
Size: 2618 bytes
Desc: not available
URL: <http://lists.racket-lang.org/dev/archive/attachments/20100402/c103006d/attachment.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sstruct-tests.ss
Type: application/octet-stream
Size: 5538 bytes
Desc: not available
URL: <http://lists.racket-lang.org/dev/archive/attachments/20100402/c103006d/attachment-0001.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sstruct.ss
Type: application/octet-stream
Size: 7658 bytes
Desc: not available
URL: <http://lists.racket-lang.org/dev/archive/attachments/20100402/c103006d/attachment-0002.obj>

Posted on the dev mailing list.