AlbusMPiroglu
AlbusMPiroglu

Reputation: 653

loading a common racket header into current toplevel

There's a simple code section I'd like to share among a few racket scripts I wrote. I don't want to (require ..) it, but want to run it at top level (the common code includes calls to (current-command-line-arguments), etc. that a required module doesn't understand (at least I don't know if that's possible).

I try (load)'ing it, but it doesn't work as it was part of the current top-level context - I get an unbound identifier error in the loading context for a definition that's defined in the common header.

It seems I still don't understand some sections of the chapter 14 of the racket reference - I tried some examples of 14.1 (namespaces), 14.3 (racket/load) but couldn't get it working.

What am I missing? How can we make such a seemingly simple case work?

Edit: Here is an example from my case:

this is header.rkt:

#lang racket/load

(define-for-syntax args (current-command-line-arguments))
(define-for-syntax PROTOTYPING (or
                            (= (vector-length args) 0)
                            (equal? (vector-ref args 0) "local")))

(define-syntax (if-prototyping stx)
  (syntax-case stx ()
    ((_ debug-expr non-debug-expr)
     (if PROTOTYPING
         #'debug-expr
         #'non-debug-expr))))

(if-prototyping
 (require (prefix-in "ci:" (file "/debugenvpath/utils.rkt")))
 (require (prefix-in "ci:" (file "/releaseenvpath/utils.rkt"))))

; arrange arguments
(define args (current-command-line-arguments))
(if-prototyping
 (set! args (if (= (vector-length args) 0)
                (vector "arg1")
                (vector (vector-ref args 1))))
 #t)

and this is a file that uses it (let's call this test.bat):

; @echo off
; "C:\Program Files\Racket\racket.exe" "%~f0" %*
; exit /b
#lang racket

(require "header.rkt")

"hello, args are: "
args

and running test.bat gives error below for the last line of the file:

args: unbound identifier in module in: args

Update2

OK, here is an update with phase1 (so-called macro-time) & phase0 (so-called runtime) expansion of my approach:

I can run (current-command-line-arguments) in phase1 (macro) when everything is in one file:

#lang racket

(define-for-syntax args (current-command-line-arguments))
(define-for-syntax PROTOTYPING (or
                                (begin
                                  (printf "phase1 expansion, args are: ~a\n" args)
                                  (= (vector-length args) 0))
                                (equal? (vector-ref args 0) "local")))
(begin-for-syntax 
  (printf "phase1, val for PROTOTYPING is: ~a\n" PROTOTYPING))

(define-syntax (if-prototyping stx)
  (syntax-case stx ()
    ((_ debug-expr non-debug-expr)
     (if PROTOTYPING
         #'debug-expr
         #'non-debug-expr))))

(if-prototyping
 (require (prefix-in "ci:" (file "./debug-env/util.rkt")))
 (require (prefix-in "ci:" (file "./release-env/util.rkt"))))

; arrange arguments
(define args (current-command-line-arguments))
(printf "phase0 expansion, args are: ~a\n" args)
(if-prototyping
 (set! args (if (= (vector-length args) 0)
                (vector "my-testing-arg-to-use-from-drracket")
                (vector (vector-ref args 1))))
 #t)

"util func call: "
(ci:f)

have util.rkt files for double checking: debug-env/util.rkt:

#lang racket
(provide f)
(define (f) (display "f from debug"))

release-env/util.rkt:

#lang racket
(provide f)
(define (f) (display "f from release"))

and this is the output of the test run:

>test-commonheader.bat local arg1 arg2
phase1 expansion, args are: #(local arg1 arg2)
phase1, val for PROTOTYPING is: #t
phase0 expansion, args are: #(local arg1 arg2)
"util func call: "
f from debug

>test-commonheader.bat arg1 arg2
phase1 expansion, args are: #(arg1 arg2)
phase1, val for PROTOTYPING is: #f
phase0 expansion, args are: #(arg1 arg2)
#t
"util func call: "
f from release

it works as I expected. Now, the problem standing is, I can't put this whole logic in a common-header.rkt file to reuse. I'm just doing some more reading on Racket Reference 3.2 (Importing and Exporting), and there's a lot more that I should understand on require'ing for-syntax or directly to a phase level and provide'ing to phase-levels. I progressed a bit more with (for-syntax) in require and provide, but not there yet :/

update 3

It seems I was too busy trying to understand the phase-level extensions I missed the basics again. A simple (provide if-prototyping) from the header and (require "header.rkt") is actually working - but not perfectly :( . Alex was somehow correct in suggesting require should work. I was trying to (provide (for-syntax if-prototyping)) but that doesn't work because the if-prototyping is used at phase0 in the importing module.

Here's the latest header:

#lang racket

(provide if-prototyping)

(define-for-syntax args (current-command-line-arguments))
(define-for-syntax PROTOTYPING (or
                                (begin
                                  (printf "phase1 expansion, args are: ~a\n" args)
                                  (= (vector-length args) 0))
                                (equal? (vector-ref args 0) "local")))
(begin-for-syntax
  (printf "phase1, val for PROTOTYPING is: ~a\n" PROTOTYPING))

(define-syntax (if-prototyping stx)
  (syntax-case stx ()
    ((_ debug-expr non-debug-expr)
     (if PROTOTYPING
         #'debug-expr
         #'non-debug-expr))))

(if-prototyping
 (begin
   (require "debug-env/util.rkt")
   (provide (all-from-out "debug-env/util.rkt")))
 (begin
   (require "release-env/util.rkt")
   (provide (all-from-out "release-env/util.rkt"))))

and in the test-commonheader.bat I require the header:

(require "header.rkt")

now, a new problem is, header.rkt phase1 definitions run twice (which may be alright for this case - that's why I'll accept Alex's answer on using require):

>test-commonheader.bat arg1 arg2
phase1 expansion, args are: #(arg1 arg2)
phase1, val for PROTOTYPING is: #f
phase1 expansion, args are: #(arg1 arg2)
phase1, val for PROTOTYPING is: #f
phase0 expansion, args are: #(arg1 arg2)
#t
"util func call: "
f from release

Upvotes: 4

Views: 368

Answers (2)

AlbusMPiroglu
AlbusMPiroglu

Reputation: 653

Addendum - follow up to the answer and discussions

Thanks to Alex's link for Matthew's presentation, I found one of the answers at 39:31. (require (for-syntax racket/base)) explains the missing part.

I'll briefly explain here. To start with, I must change the jargon of "compile time" and "run time" to phase1 & phase0. You can run any code at "any"-time with racket. You just have to put the necessary bindings into the scope that you're at.

When a code is running at a phaseN, it can run a compiled code or can do a syntax transformation, that's irrelevant - which means using the words "compile-time" and "runtime" will necessarily confuse people, just because of their stereotypical exposure to the phenomena using their inferior programming languages.

When you don't do any macro programming, everything is in phase0 (so-called runtime). Phases are in a way additions to your scopes in terms of how the bindings are maintained (probably a prefix to the identifiers of bindings, in a table where all the bindings live).

When a piece of code is run, it checks whether all the necessary bindings are reachable from it's scope. That means it doesn't matter if we're at phase0 (runtime), or phase1 (compile-time), or phaseN (runtime according to itself, compile-time according to phase(N-1)). What matters is whether the bindings are reachable from that phase. In our example question above, (current-command-line-arguments) is just a binding that needs to be brought to the current phase with some evaluation.

This is how we do it:

  • If we want to use it at current phase, we (require racket/base) or since I never tried that, we rather use #lang racket/base.
  • If we want to use it at current phase+1 (i.e. compile-time), we just (require (for-syntax racket/base)) to bring (current-command-line-arguments) into current phase+1.

let's see it in action:

If we try to use (current-command-line-arguments) in ..for-syntax.. forms, with only #lang racket/base, since it's not bound in phase1, it'll indicate it with an error:

#lang racket/base
(define-for-syntax args (current-command-line-arguments))
(define-for-syntax PROTOTYPING (or (= (vector-length args) 0)
                                   (equal? (vector-ref args 0) "local")))
(begin-for-syntax
  (printf "phase1, val for PROTOTYPING is: ~a\n" PROTOTYPING))

output of above is:

current-command-line-arguments: unbound identifier in the transformer environment;
 also, no #%app syntax transformer is bound in: current-command-line-arguments

but if we add (2nd line) the necessary require:

#lang racket/base
(require (for-syntax racket/base))
(define-for-syntax args (current-command-line-arguments))
(define-for-syntax PROTOTYPING (or (= (vector-length args) 0)
                                   (equal? (vector-ref args 0) "local")))
(begin-for-syntax
  (printf "phase1, val for PROTOTYPING is: ~a\n" PROTOTYPING))

then the output is (depends where you're running it from, here a drracket run to REPL is shown):

phase1, val for PROTOTYPING is: #t
phase1, val for PROTOTYPING is: #t
phase1, val for PROTOTYPING is: #t

Why does it depend, and why are there multiple lines? I'm not 100% sure, but have a feeling that it has something to do with the "Separate Compilation Warranty", Racket Reference 1.1.10.2. Because, as you can see the output we see is an "external" effect (output to an I/O device - this case the screen), which a separate compilation cannot guarantee to be stable (or bound?).

But (it seems?) if you want to do syntax-transformations with dynamic behaviour, by all means you can use:

#lang racket/base
(require (for-syntax racket/base))

or simply use below which does the above and some more (also increasing memory use of the session, of course):

#lang racket

Upvotes: 1

Alex Knauth
Alex Knauth

Reputation: 8373

If you need to use current-command-line-arguments at run-time, using require should work for that. Here's a simple example:

In common-code.rkt:

#lang racket
(current-command-line-arguments)
(current-command-line-arguments (vector "helloo"))

In file.rkt:

#lang racket
(require "common-code.rkt")
(current-command-line-arguments)

When file.rkt is run with the command-line arguments these are my arguments, it prints this:

'#("these" "are" "my" "arguments")
'#("helloo")

When a file is required another file, it is always run when the other file is run, and its run-time effects, including things like current-command-line-arguments, are seen at run-time by the code the other file. Also, parameters like current-command-line-arguments are for the current script that is running, not the current file.

Alternatively, a slightly better way would be to make the common code a function, like this:

In common-code.rkt:

#lang racket
(provide setup)
(define (setup)
  (println (current-command-line-arguments))
  (current-command-line-arguments (vector "helloo")))

In file.rkt:

#lang racket
(require "common-code.rkt")
(setup)
(current-command-line-arguments)

This run with these are my arguments also prints out

'#("these" "are" "my" "arguments")
'#("helloo")

This way, file.rkt has more control over if/when/in-what-context (setup) gets called, and it can also can pass in arguments to the function (if you modify it to accept them), which can be very useful if you want the common code to rely on something from file.rkt.

Update

With your updated question, the real problem seems to be that you want to require a different file depending on the command-line arguments. If there are no arguments or if the first argument is debug, you want the debugging version of the file, and if the first argument is anything else, you want the non-debugging version of the file.

The two versions of the file provide the same bindings with the same purpose, in other words, they have the same interface. And because they have the same interface, there are several ways to do this.

Functions

The way that uses the least magic is to have common-code.rkt provide a function which returns all of the functions from either the debug or non-debug versions:

non-debug.rkt:

#lang racket
(provide f g)
(define (f x)
  (add1 x))
(define (g x)
  (* 2 x))

debug.rkt:

#lang racket
(provide f g)
(require (prefix-in - "non-debug.rkt"))
(define (f x)
  (printf "(f ~v)\n" x)
  (-f x))
(define (g x)
  (printf "(g ~v)\n" x)
  (-g x))

common-code.rkt:

#lang racket
(provide return-f-and-g)
(define (return-f-and-g)
  (define args (current-command-line-arguments))
  (define debug? (or (= (vector-length args) 0)
                     (equal? (vector-ref args 0) "debug")))
  (cond [debug?
         (local-require "debug.rkt")
         (values f g)]
        [else
         (local-require "non-debug.rkt")
         (values f g)]))

file.rkt:

#lang racket
(require "common-code.rkt")
(define-values [f g]
  (return-f-and-g))
(f 2)
(g 2)

However, if debug.rkt and non-debug.rkt provide many functions, you'll have to enumerate all of them twice in common-code.rkt, and once in each file that you use it in.

The dynamic-require function

Another way to do this is with dynamic-require. It would be used like this in common-code.rkt:

#lang racket
(provide f g)
(define args (current-command-line-arguments))
(define debug? (or (= (vector-length args) 0)
                   (equal? (vector-ref args 0) "debug")))
(define debug-on-non-debug (if debug? "debug.rkt" "non-debug.rkt"))
(define f (dynamic-require debug-on-non-debug 'f))
(define g (dynamic-require debug-on-non-debug 'g))

file.rkt:

#lang racket
(require "common-code.rkt")
(f 2)
(g 2)

However, this still leaves the problem of enumerating every single binding and writing a (define f (dynamic-require debug-on-non-debug 'f)) line for each one.

Units

I said before that each of these methods was possible because debug.rkt and non-debug.rkt provided the same interface. One way of expressing that in racket is with Units. They provide a way to package these things up into first-class values that can be chosen at run-time. Units have their own notion of an interface, called a signature.

You can declare the interface of the debug and non-debug files by using #lang racket/signature. So in debug-or-non-debug-sig.rkt, put:

#lang racket/signature
f ; takes a number and adds 1 to it
g ; takes a number and multiplies it by 2

That defines the interface. It's good practice to also write comments specifying what f and g do, what they take as arguments, what they return, etc.

Then you have to declare that the debug and non-debug files implement that interface. You can do that with #lang racket/unit and the export form.

non-debug-unit.rkt:

#lang racket/unit
(require "debug-or-non-debug-sig.rkt")
(import)
(export debug-or-non-debug^)
(define (f x)
  (add1 x))
(define (g x)
  (* 2 x))

debug-unit.rkt:

#lang racket
(require "debug-or-non-debug-sig.rkt")
(import)
(export debug-or-non-debug^)
(define (f x)
  (printf "(f ~v)\n" x)
  (add1 x))
(define (g x)
  (printf "(g ~v)\n" x)
  (* 2 x))

Then you can choose at run-time which of these files to use, with the form define-values/invoke-unit.

common-code.rkt:

#lang racket
(provide (all-defined-out))
(require "debug-or-non-debug-sig.rkt"
         "debug-unit.rkt"
         "non-debug-unit.rkt")
(define args (current-command-line-arguments))
(define debug? (or (= (vector-length args) 0)
                   (equal? (vector-ref args 0) "debug")))
(define-values/invoke-unit (if debug? debug@ non-debug@)
  (import)
  (export debug-or-non-debug^))

file.rkt:

#lang racket
(require "common-code.rkt")
(f 2)
(g 2)

If file.rkt is run with no command-line arguments or with debug as the first argument, it prints this:

(f 2)
3
(g 2)
4

But if it is run with any other arguments, it prints this:

3
4

Upvotes: 3

Related Questions