Search code examples
racket

-D option and #ifdef in Racket


In C, you can compile with option, say, -DPROMPT. Then, in the file you can have #ifdef PROMPT and the code will be transformed at compile time to include things in that block.

Is there something similar in Racket? I tried racket --help but didn't see any option that could be useful.


Solution

  • Yes there is, but it works a little differently than it does in C. As Alexis said, Racket compilation is semi-invisible. However, there are tools in the Racket eco system that do exactly this, the most popular being errortrace.

    Any time you use define-syntax, you are writing a compiler plug-in, if you will. The main difference between this and C is that, in C, you are required to write these extensions in a half-baked DSL known as preprocessor directives, where as in Racket, you can write these extensions with the full power of, well, Racket.

    This means that any construct you used in Racket to talk to the outside world, you can also use here. One example, as Jens suggested was to use environment variables. But since you explicitly asked for the ability to pass in arguments, you can use either current-command-line-arguments, or better still, the command-line module, which parses the command line arguments for you.

    As an example of how this works, the following file will compile to either display the symbol 'build, or 'no-build, depending on if any arguments were passed on the command line:

    #lang racket
    
    (require (for-syntax syntax/parse
                         racket/cmdline))
    
    (define-syntax (arguments? stx)
      (syntax-parse stx
        [(_)
         (if (null?
              (command-line
               #:args args
               args))
             #''no-build
             #''build)]))
      
    (arguments?)
    

    The way this differs from just having the if here without the define-syntax is that in the first case, this happens at compile time, rather than at run time, so to speak.

    This is most evident when you compile your code at a different time than when you run it, using the raco make command.

    First, lets try running and compiling at the same time:

    $ racket example.rkt
    'no-build
    $ racket example.rkt an-argument
    'build
    

    However, now lets try compiling it before we run it, using raco make:

    $ raco make example.rkt
    $ racket example.rkt
    'build
    

    See here that even though we are not passing any arguments to example.rkt, it still returns 'build. This is because the choice is made entirely at compile time, when raco make is run. The call to raco make does have 1 argument: the file example.rkt, as such, it gets compiled simply display 'build.

    This operation can be undone by deleting the compiled file:

    $ rm compiled/example_rkt.*
    

    And re-running the example:

    $ racket example.rkt
    'no-build
    

    In fact, you can even remove the example.rkt file once it is compiled:

    $ raco make example.rkt
    $ rm example.rkt
    $ racket example.rkt
    'build
    

    Although don't do this in general because unless you have your source backed up somewhere, you will no longer be able to retrieve it.

    Finally, if you want to use the exact same -D syntax you used in C, the command-line has a #:multi option which you can use to set how you would like your code to compile.

    Just as one last note however, do note that compiling racket files differs from compiling C files in that the generated zo files are transient. They will only work on the current version of the Racket VM, and since that is updated frequently (several times a year), you are much better off distributing source code. If you want to obfuscate it in a similar way to distributing C binaries, you can also distribute your fully expanded code, but that is an answer for a different question.