[racket] Scoped require
Maxim,
It is possible to write a macro that "hides" local-require, although
(let ()
(local-require 'names)
my-code ...)
looks about as simple to me as
(require 'names)
(my-begin
my-code ...)
and the former does not require implementing any new unhygienic
macros. The local-require macro is itself unhygienic, but at least it
has already been implemented. Still, here's how you can write your
macro:
(require
(for-syntax syntax/parse)
racket/splicing)
(define-syntax (my-begin stx)
(syntax-parse stx
[(_ body:expr ...)
(with-syntax
([imports
(datum->syntax stx
(syntax-e (syntax 'real-names)))])
(syntax
(splicing-local
[(local-require imports)]
body ...)))]))
Here, "real-names" is the name of the module providing everything that
will be bound within my-begin, and the "names" module will provide
only my-begin.
--Carl
On Tue, Aug 23, 2011 at 3:22 AM, Maxim Romashchenko <max at anahoret.com> wrote:
> Thank you, Matthias and Carl, for a detailed reply.
>
> I'm now much close to my goal. Almost there.
>
> The approach with non-hygienic macro (item 6 in your email) works, but it
> requires each symbol to be introduced explicitly (twice, with two different
> names -- f and ff in your example).
>
> On the other hand, local-require (item 4) accomplishes almost the same thing
> in a much clear way.
>
> (I'm not yet familiar with units, so I did not fully understand item 5, but
> I assume it does essentially the same thing as local-require, but using
> define-values/invoke-unit.)
>
> I wonder is there a way to hide require-local inside a macro, so it will be
> implicit? In other words, is it possible to keep the user code as simple as
>
> (module uses racket
> (require 'names)
> (my-names-begin
> (f)))
>
> but still to be able to access all the symbols from the module inside the
> my-name-begin form, as if there is require-local call?
>
> Thank you.
>
> Best regards, Maxim.
>
>
> On 2011-08-22 18:05, Matthias Felleisen wrote:
>>
>> Maxim, let me re-order Carl's message.
>>
>> 1. You are trying to implement a non-hygienic macro. That is, your
>> my-begin macro is supposed to bind names in its expressions that are
>> not in the scope of the expressions.
>>
>> 2. Racket's macro system is hygienic, that is, the default does not
>> allow such scope manipulations. The assumption behind this default is
>> that such manipulations are usually faulty and introduce subtle (as
>> in difficult to find) errors.
>>
>> 3. As the word 'default' suggests, there are ways around this
>> restriction -- but they tell the programmer to be careful. [[
>> Surprisingly, it also means that the seemingly-overly-restrictive
>> hygienic macro system has more expressive power than the plain Lisp
>> macro system. ]]
>>
>> 4. Since we don't quite understand your actual goal, Carl lists a
>> number of alternatives. One is to locally require a module into a
>> scope definition context:
>>
>> #lang racket/load
>>
>> (module names racket/base
>> (provide f)
>> (define (f) (displayln "hello world")))
>>
>> (module uses racket
>> (require 'names)
>> (let ()
>> (local-require 'names)
>> (f)))
>>
>> (require 'uses)
>>
>> But, as it turns out to my surprise, local-require pollutes the global
>> scope:
>>
>> #lang racket/load
>>
>> (module names racket/base
>> (provide f)
>> (define (f) (displayln "hello world")))
>>
>> (module uses-bad racket
>> (require 'names)
>> (f)
>> (let ()
>> (local-require 'names)
>> (f)))
>>
>> (require 'uses-bad)
>>
>> Perhaps I just misunderstand.
>>
>> 5. An alternative is to define a unit (a first-class module), to export it
>> and its signature, and to splice its definitions into a local scope, like
>> this:
>>
>> #lang racket/load
>>
>> (module a racket
>> (provide a@ a^)
>> (define-signature a^ (f))
>> (define a@ (unit (import) (export a^) (define (f) (displayln "hello
>> world")))))
>>
>> ;; testing the unit splice
>> (module c racket
>> (require 'a)
>> (let ()
>> (define-values/invoke-unit a@ (import) (export a^))
>> (f)))
>>
>> ;; testing its locality
>> (with-handlers ((exn:fail:syntax? (lambda (x)
>> (define faulty-expressions
>> (exn:fail:syntax-exprs x))
>> (define faulty-name (map syntax-e
>> faulty-expressions))
>> (displayln `(,(exn-message x)
>> , at faulty-name)))))
>> (eval '(module c racket
>> (require 'a)
>> (let ()
>> (define-values/invoke-unit a@ (import) (export a^))
>> (f))
>> f)))
>>
>> (require 'c)
>>
>> 6. Last but not least, you could define an unhygienic macro like this:
>>
>> #lang racket/load
>>
>> (module names racket
>> (provide (rename-out (my-begin begin)))
>>
>> (require (for-syntax syntax/parse))
>>
>> (define (f) (displayln "hello world"))
>> (define-syntax (my-begin stx)
>> (syntax-parse stx
>> ((_ body:expr ...)
>> (let ((ff (datum->syntax stx 'f))) ;; breaking hygiene
>> #`(let ()
>> (define #,ff f)
>> body
>> ...))))))
>>
>>
>> (module uses racket
>> (require 'names)
>> (begin
>> (f)))
>>
>> (require 'uses)
>>
>
>>
>>
>> On Aug 22, 2011, at 10:23 AM, Carl Eastlund wrote:
>>
>>> Maxim,
>>>
>>> There are a few tools that might accomplish what you want. To have
>>> scoped definitions available to a set of top-level definitions (i.e.
>>> those inside my-begin), use splicing-local from racket/splicing. To
>>> make a set of definitions available at one place, you could package
>>> them up as a unit and use define-values/invoke-unit, or as a package
>>> and use open-package, or as a module (separate from the one with
>>> my-begin) and use local-require. In all of these cases, the binding
>>> of those forms is unhygienic (you are binding names that are not given
>>> by the user of the my-begin macro), so you may have to do some direct
>>> manipulation of syntax objects to get the scope how you want it. I
>>> hope I've at least given you some useful starting places.
>>>
>>> Carl Eastlund
>>>
>>> On Mon, Aug 22, 2011 at 10:04 AM, Maxim Romashchenko<max at anahoret.com>
>>> wrote:
>>>>
>>>> Hello.
>>>>
>>>> Thank you for your reply, Eli.
>>>> It looks like I need to state my question more clearly.
>>>>
>>>> The trick I'm looking for is how to create a quasi-begin form inside
>>>> which
>>>> you can use all the other symbols defined in the module, while those
>>>> symbols
>>>> are not imported into top-level. In fact the only thing added to the top
>>>> level is supposed to be the quasi-begin form itself.
>>>>
>>>> Best regards, Maxim.
>>>>
>>>>
>>>> On 2011-08-22 16:04, Eli Barzilay wrote:
>>>>>
>>>>> 50 minutes ago, Maxim Romashchenko wrote:
>>>>>>
>>>>>> --- my-module.rkt ---
>>>>>> #lang racket
>>>>>> (provide my-begin)
>>>>>>
>>>>>> (define foo
>>>>>> ...
>>>>>> ---------------------
>>>>>
>>>>> You could do this:
>>>>>
>>>>> #lang racket
>>>>> (provide (rename-out [begin my-begin]))
>>>>>
>>>>> and get what you want,
>>>>>
>>>>>> --- main.rkt ---
>>>>>> #lang racket
>>>>>> (require "my-module.rkt")
>>>>>>
>>>>>> (my-begin
>>>>>> (foo
>>>>>> ...
>>>>>> -----------------
>>>>>
>>>>> but it's probably easier to do this instead here:
>>>>>
>>>>> #lang racket
>>>>> (require (rename-in racket [begin my-begin]))