From 851f98a6d1e788d4bc3096312e8b639c4a2314b9 Mon Sep 17 00:00:00 2001 From: Ben Greenman Date: Tue, 21 Sep 2021 15:29:01 -0400 Subject: [PATCH] add fresh-variable from https://github.com/syntax-objects/Summer2021/issues/22 cc @shhyou --- fresh-variable/fresh-variable-test.rkt | 50 ++++++++++++++++ fresh-variable/fresh-variable.rkt | 13 +++++ fresh-variable/fresh-variable.scrbl | 81 ++++++++++++++++++++++++++ index.scrbl | 1 + 4 files changed, 145 insertions(+) create mode 100644 fresh-variable/fresh-variable-test.rkt create mode 100644 fresh-variable/fresh-variable.rkt create mode 100644 fresh-variable/fresh-variable.scrbl diff --git a/fresh-variable/fresh-variable-test.rkt b/fresh-variable/fresh-variable-test.rkt new file mode 100644 index 0000000..05bbdba --- /dev/null +++ b/fresh-variable/fresh-variable-test.rkt @@ -0,0 +1,50 @@ +#lang racket/base +(module+ test + (require rackunit syntax/macro-testing + (for-syntax racket/base syntax/parse syntax/transformer syntax-parse-example/fresh-variable/fresh-variable)) + + (define-syntax (define/immutable-parameter stx) + (syntax-parse stx + [(_ (name:id (~or* (~and _:id arg:fresh-variable) + [(~and _:id arg:fresh-variable) default-value:expr]) + ...) + body:expr ...+) + #'(define (name (~? [arg.fresh-var default-value] + arg.fresh-var) ...) + ;; disable set! on arg + (define-syntax arg + (make-variable-like-transformer #'arg.fresh-var #f)) + ... + body ...)])) + + (check-exn #rx"set!: cannot mutate identifier" + (lambda () + (convert-compile-time-error + (let () + (define/immutable-parameter (bad-fn n) + (set! n 12345) ;=> syntax error + (void)) + (void))))) + + (check-exn #rx"set!: cannot mutate identifier" + (lambda () + (convert-compile-time-error + (let () + (define/immutable-parameter (bad-fn-optional n [verbose? #f]) + (set! verbose? 12345) ;=> syntax error + (void)) + (void))))) + + (define/immutable-parameter (fib n [verbose? #f]) + (when verbose? + (printf "(fib ~a)\n" n)) + (cond + [(<= n 1) n] + [else + (+ (fib (- n 1) verbose?) + (fib (- n 2) verbose?))])) + + (check-equal? (fib 4) 3) + (check-equal? (fib 8) 21) + +) diff --git a/fresh-variable/fresh-variable.rkt b/fresh-variable/fresh-variable.rkt new file mode 100644 index 0000000..f01a7d0 --- /dev/null +++ b/fresh-variable/fresh-variable.rkt @@ -0,0 +1,13 @@ +#lang racket/base + +(provide fresh-variable) + +(require racket/syntax syntax/parse) + +(define-syntax-class (fresh-variable [context #f]) + #:attributes (fresh-var) + (pattern name + #:with temp-var (generate-temporary #'name) + #:with fresh-var (if context + (format-id context "~a" #'temp-var) + #'temp-var))) diff --git a/fresh-variable/fresh-variable.scrbl b/fresh-variable/fresh-variable.scrbl new file mode 100644 index 0000000..cf1204e --- /dev/null +++ b/fresh-variable/fresh-variable.scrbl @@ -0,0 +1,81 @@ +#lang syntax-parse-example +@require[ + (for-label racket/base racket/syntax syntax/transformer syntax/parse syntax-parse-example/fresh-variable/fresh-variable)] + +@(define fresh-variable-eval + (make-base-eval '(require (for-syntax racket/base syntax/parse syntax/transformer syntax-parse-example/fresh-variable/fresh-variable)))) + +@title{Generate Temporaries On The Fly: @tt{fresh-variable} Syntax Class} +@stxbee2021["shhyou" 22] + +@; ============================================================================= + +@defmodule[syntax-parse-example/fresh-variable/fresh-variable]{} + +Some macros need to generate a sequence of fresh identifiers corresponding to a +list of input forms. The standard solution is to invoke @racket[generate-temporaries] +with a syntax list and bind the result to a new pattern variable. However, the +new pattern variable is disconnected from the input forms and such an approach +quickly becomes unmanageable when the input forms come nested in more than one +ellipses. + +The @racket[fresh-variable] syntax class solves both these issues. +First, it tightly couples generated identifiers to the input forms. +The new identifiers even have DrRacket binding arrows. +Second, it leverages the @racket[syntax-parse] pattern matcher to handle +deeply-nested repetitions. + +@defidform[fresh-variable]{ + Syntax class that binds an attribute @racket[_fresh-var] to a fresh temporary + variable. + + In the example below, we create a macro @racket[define/immutable-parameter] + for defining functions whose parameters cannot be mutated by the function + body. + The macro parses arguments using the @racket[fresh-variable] syntax class + to generate temporary identifiers on the fly. + + @examples[#:eval fresh-variable-eval + (define-syntax (define/immutable-parameter stx) + (syntax-parse stx + [(_ (name:id (~or* (~and _:id arg:fresh-variable) + [(~and _:id arg:fresh-variable) default-value:expr]) + ...) + body:expr ...+) + #'(define (name (~? [arg.fresh-var default-value] + arg.fresh-var) ...) + ;; disable set! on arg + (define-syntax arg + (make-variable-like-transformer #'arg.fresh-var #f)) + ... + body ...)])) + + (define/immutable-parameter (fib n [verbose? #f]) + (code:comment "(set! n 12345) ;=> syntax error") + (when verbose? + (printf "(fib ~a)\n" n)) + (cond + [(<= n 1) n] + [else + (+ (fib (- n 1) verbose?) + (fib (- n 2) verbose?))])) + + (fib 5) + ] + + The implementation accepts any expression and generates a temporary identifier. + + @racketfile{fresh-variable.rkt} + + @itemize[ + @item{ + @bold{Q.} Why use @racket[_name] instead of asking for an identifier with @racket[_name:id]? + } + @item{ + @bold{A.} Some macros may let-bind subforms to first evaluate them for + later use. Therefore the subforms can be any expressions. I couldn't find + a way to pass syntax classes around or compose them, so the @racket[_:id] + specification is left out of the syntax class. + } + ] +} diff --git a/index.scrbl b/index.scrbl index 0219dcb..a5ad43b 100644 --- a/index.scrbl +++ b/index.scrbl @@ -44,3 +44,4 @@ @include-example{js-dict} @include-example{define-freevar} @include-example{fnarg} +@include-example{fresh-variable} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy