On this page:
7.1 Library Interfaces
7.2 Macros:   Space and Performance
7.3 Unsafe:   Beware

7 Language and Performance

When you write a module, you first pick a language. In Racket you can choose a lot of languages. The most important choice concerns racket/base vs racket.

For scripts, use racket/base. The racket/base language loads significantly faster than the racket language because it is much smaller than the racket.

If your module is intended as a library, stick to racket/base. That way script writers can use it without incurring the overhead of loading all of racket unknowingly.

Conversely, you should use racket (or even racket/gui) when you just want a convenient language to write some program. The racket language comes with almost all the batteries, and racket/gui adds the rest of the GUI base.

7.1 Library Interfaces

Imagine you are working on a library. You start with one module, but before you know it the set of modules grows to a decent size. Client programs are unlikely to use all of your library’s exports and modules. If, by default, your library includes all features, you may cause unnecessary mental stress and run-time cost that clients do not actually use.

In building the Racket language, we have found it useful to factor libraries into different layers so that client programs can selectively import from these bundles. The specific Racket practice is to use the most prominent name as the default for the module that includes everything. When it comes to languages, this is the role of racket. A programmer who wishes to depend on a small part of the language chooses to racket/base instead; this name refers to the basic foundation of the language. Finally, some of Racket’s constructs are not even included in racketconsider racket/require for example—and must be required explicitly in programs.

Other Racket libraries choose to use the default name for the small core. Special names then refer to the complete library.

We encourage library developers to think critically about these decisions and decide on a practice that fits their taste and understanding of the users of their library. We encourage developers to use the following names for different places on the "size" hierarchy:

  • library/kernel, the bare minimal conceievable for the library to be usable;

  • library/base, a basic set of functionality.

  • library, an appropriate "default" of functionality corresponding to either library/base or library/full.

  • library/full, the full library functionality.

Keep two considerations in mind as you decide which parts of your library should be in which files: dependency and logical ordering. The smaller files should depend on fewer dependencies. Try to organize the levels so that, in principle, the larger libraries can be implemented in terms of the public interfaces of the smaller ones.

Finally, the advice of the previous section, to use racket/base when building a library, generalizes to other libraries: by being more specific in your dependencies, you are a responsible citizen and enable others to have a small (transitive) dependency set.

7.2 Macros: Space and Performance

Macros copy code. Also, Racket is really a tower of macro-implemented languages. Hence, a single line of source code may expand into a rather large core expression. As you and others keep adding macros, even the smallest functions generate huge expressions and consume a lot of space. This kind of space consumption may affect the performance of your project and is therefore to be avoided.

When you design your own macro with a large expansion, try to factor it into a function call that consumes small thunks or procedures.

good

#lang racket
...
(define-syntax (search s)
  (syntax-parse s
    [(_ x (e:expr ...)
        (~datum in)
        b:expr)
     #'(sar/λ (list e ...)
              (λ (x) b))]))
 
(define (sar/λ l p)
  (for ((a '())) ((y l))
    (unless (bad? y)
      (cons (p y) a))))
 
(define (bad? x)
  ... many lines ...)
...

bad

#lang racket
...
(define-syntax (search s)
  (syntax-parse s
    [(_ x (e:expr ...)
       (~datum in)
       b:expr)
     #'(begin
         (define (bad? x)
           ... many lines ...)
         (define l
           (list e ...))
         (for ((a '())) ((x l))
           (unless (bad? x)
             (cons b a))))]))

As you can see, the macro on the left calls a function with a list of the searchable values and a function that encapsulates the body. Every expansion is a single function call. In contrast, the macro on the right expands to many nested definitions and expressions every time it is used.

7.3 Unsafe: Beware

Racket provides a number of unsafe operations that behave like their related, safe variants but only when given valid inputs. They differ in that they eschew checking for performance reasons and thus behave unpredictably on invalid inputs.

As one example, consider fx+ and unsafe-fx+. When fx+ is applied to a non-fixnum?, it raises an error. In contrast, when unsafe-fx+ is applied to a non-fixnum?, it does not raise an error. Instead it either returns a strange result that may violate invariants of the run-time system and may cause later operations (such as printing out the value) to crash Racket itself.

Do not use unsafe operations in your programs unless you are writing software that builds proofs that the unsafe operations receive only valid inputs (e.g., a type system like Typed Racket) or you are building an abstraction that always inserts the right checks very close to the unsafe operation (e.g., a macro like for). And even in these situations, avoid unsafe operations unless you have done a careful performance analysis to be sure that the performance improvement outweighs the risk of using the unsafe operations.