[racket] requiring a file in current-directory, if it exists

From: Gustavo Massaccesi (gustavo at oma.org.ar)
Date: Sat Oct 25 21:23:56 EDT 2014

There is a lot of magic inside the require macro and
require-transformers. I still don't understand all the details, but I
hope I got most of them right. With some simplifications it's easier
to understand.

First, we create two dummy modules

;--- a.rkt ---
#lang racket
(provide a)
(define a "A!")
;------

;--- b.rkt ---
#lang racket
(provide b)
(define a "B!")
;------

Now, we require them using simplified versions of your macros

;--- prefied.rkt ---
#lang racket
(define-syntax (old-local-require stx)
  (syntax-case stx ()
    [(_ filename)
     (with-syntax ([path-string (datum->syntax stx (syntax->datum #'filename))])
       #'(begin
           (require (prefix-in local: (file path-string)))
           local:a ;==> "A!"
           ))]))

(define-syntax (new-local-require stx)
  (syntax-case stx ()
    [(_ filename)
     (with-syntax ([file-path (datum->syntax stx `(file
,(syntax->datum #'filename)))])
       #'(begin
           (require (prefix-in local: file-path))
           ;local:b ;==> <error>
           ))]))


(old-local-require "a.rkt")
;local:a ;==> <error>
(new-local-require "b.rkt")
local:b ;==> "B!"
;------

If you paste this in DrRacket and use the Macro Stepper, then the
simplified version of the last expansion is

;[...]
(#%require:52 (rename:52 (file:52 "a.rkt") local:a:53 a:52))
(print-values:54 local:a:53)
(#%require:55 (rename:55 (file:55 "b.rkt") local:b b:55))
(print-values:56 local:b)
;

The "file" require-transformer use the marks of the "(file ...)"
expression to mark the required identifiers.



* In the first "old" case, the initial expression "(file ...)" has no
marks, and then local:a has initially no marks, but then the
require-transformer macro add the marks to preserve hygiene.

In the Macro Stepper, you see that in the final expression, inside the
#%require you get local:a:53 (where 53 somehow indicate the set of
marks that local:a has). So the new identifiers are required
hygienically and you can't call them from outside the macro.



* In the second "new" case, datum->syntax copies the marks from "stx"
to "(file ...)", so it has a lot of marks, and then local:b has
initially a lot of marks. When the require-transformer macro adds the
marks to preserve hygiene, the marks cancel and the final local:b has
no marks.

Now, in the Macro Stepper, you see that in the final expansion, inside
the #%require you get local:b (without marks). So you can call it from
outside.



* There is a technical detail here (unexpected for me). In your
solution, when you moved the "(file ...)" identifier, the important
part was to move the parentheses that are around "file".

Gustavo

On Fri, Oct 24, 2014 at 10:03 PM, Matthew Butterick <mb at mbtype.com> wrote:
> This worked — for some reason, moving the '(file ...)' expression into the
> `with-syntax` expression made a difference.
>
> (define-syntax (overriding-require+provide-with-prefix stx)
>   (syntax-case stx () [(_ main override out-prefix)
>                        (let ([path-to-override (path->string (build-path
> (current-directory) (syntax->datum #'override)))])
>                          (if (file-exists? path-to-override)
>                              (with-syntax ([override (datum->syntax stx
> `(file ,(datum->syntax stx path-to-override)))])
>                                #'(begin
>                                    (require (combine-in override
> (subtract-in main override)))
>                                    (provide (prefix-out out-prefix
> (combine-out (all-from-out main) (all-from-out override))))))
>                              #'(begin
>                                  (require main)
>                                  (provide (prefix-out out-prefix
> (all-from-out main))))))]))
>
>
> (overriding-require+provide-with-prefix "main.rkt" "override.rkt"
> the-prefix:)
>
> On Fri, Oct 24, 2014 at 1:20 PM, Matthew Butterick <mb at mbtype.com> wrote:
>>
>> I know that a hard-coded absolute path can be used in `require`:
>>
>> (require (file "/path/to/directory/module.rkt"))))
>>
>> But how can a generated path be used? Like so:
>>
>> (require (file (path->string (build-path (current-directory)
>> "module.rkt"))))
>>
>> In particular, I want to `require` the file if it exists, or otherwise
>> skip it.
>>
>> I thought it should be a macro like this, but it doesn't work. The correct
>> `require` syntax pops out, but it doesn't bind any identifiers.
>>
>> (define-syntax (try-current-directory-require stx)
>>   (syntax-case stx ()
>>     [(_ filename)
>>      (with-syntax ([path-string (datum->syntax stx (path->string
>> (build-path (current-directory) (syntax->datum #'filename))))])
>>        (if (file-exists? (syntax->datum #'path-string))
>>            #'(require (prefix-in local: (file path-string)))
>>            #'(void)))]))
>>
>> (try-current-directory-require "module.rkt")
>>
>>
>> Any suggestions? Is this a job for `define-require-syntax`?
>>
>
>
> ____________________
>   Racket Users list:
>   http://lists.racket-lang.org/users
>


Posted on the users mailing list.