On this page:
7.1 Library Interfaces
7.2 Macros:   Space and Performance
7.3 No Contracts
7.4 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 conceivable 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 No Contracts

Adding contracts to a library is good.

On some occasions, contracts impose a significant performance penalty. For such cases, we recommend organizing the module into a main module as usual and a submodule called no-contract so that
  • the no-contract submodule provides the functionality without contracts,

  • the main module provides the functionality with contracts.

This section explains three strategies for three different situations and levels of implementation complexity.

We will soon supply a Reference section in the Evaluation Model chapter that explains the basics of our understanding of “safety” and link to it. Warning Splitting contracted functionality into two modules in this way renders the code in the no-contract module unsafe. The creator of the original code might have assumed certain constraints on some functions’ arguments, and the contracts checked these constraints. While the documentation of the no-contract submodule is likely to state these constraints, it is left to the client to check them. If the client code doesn’t check the constraints and the arguments don’t satisfy them, the code in the no-contract submodule may go wrong in various ways.

The first and simplest way to create a no-contract submodule is to use the #:unprotected-submodule functionality of contract-out.

good

#lang racket
 
(define state? zero?)
(define action? odd?)
(define strategy/c
  (-> state? action?))
 
(provide
  (contract-out
    (human strategy/c)
    (ai strategy/c)))
 
 
 
; - - - - - - - - - - -
; implementation
 
(define (general p)
  (lambda (_) pi))
 
(define (human x)
  ((general 'gui) x))
 
(define (ai x)
  ((general 'tra) x))

fast

#lang racket
 
(define state? zero?)
(define action? odd?)
(define strategy/c
  (-> state? action?))
 
(provide
  (contract-out
    #:unprotected-submodule
      no-contract
    (human strategy/c)
    (ai strategy/c)))
 
; - - - - - - - - - - -
; implementation
 
 (define (general s)
   (lambda (_) pi))
 
 (define (human x)
   ((general 'gui) x))
 
 (define (ai x)
   ((general 'tra) x))

The module called good illustrates what the code might look like originally. Every exported functions come with contracts, and the definitions of these functions can be found below the provide specification in the module body. The fast module on the right requests the creation of a submodule named no-contract, which exports the same identifiers as the original module but without contracts.

Once the submodule exists, using the library with or without contracts is straightforward:

needs-goodness

#lang racket
 
(require "fast.rkt")
 
human
; comes with contracts
; as if we had required
; "good.rkt" itself
 
(define state1 0)
(define state2
  (human state1))

needs-speed

#lang racket
 
(require
 (submod
   "fast.rkt"
   no-contract))
 
human
; comes without
; a contract
 
(define state*
  (build-list 0 1))
(define action*
  (map human state*))
Both modules require the fast module, but needs-goodness on the left goes through the contracted provide while needs-speed on the right uses the no-contract submodule. Tchnically, the left module imports human with contracts; the right one imports the same function without contract and thus doesn’t have to pay the performance penalty.

Notice, however, that when you run these two client modules—assuming you saved them with the correct names in some folder—the left one raises a contract error while the right one binds action* to

'(3.141592653589793 3.141592653589793)

The no-contract submodule generated by this first, easy approach retains the dependency on racket/contract at both compile and run time. Here is a variant of the above module that demonstrates this point:

problems-with-unprotected-submodule

#lang racket
 
(define state? zero?)
(define action? odd?)
(define strategy/c (-> state? action?))
 
(provide
 (contract-out
  #:unprotected-submodule no-contract
  (human strategy/c)
  (ai strategy/c)))
 
(define (general p) pi)
 
(define human (general 'gui))
 
(define ai (general 'tra))
Even though the contract-out specification seems to remove the contracts, requiring the no-contract still raises a contract error:

(require (submod "." server no-contract))

Explanation The no-contract submodule depends on the main module, so the require runs the body of the main module, and doing so checks the first-order properties of the exported values. Because human is not a function, this evaluation raises a contract error.

The second way to create a no-contract submodule requires systematic work from the developer and eliminates the run-time dependency on racket/contract. Here are the two modules from above, with the right one derived manually from the one on the left:

good2

#lang racket
 
(define state? zero?)
(define action? odd?)
(define strategy/c
  (-> state? action?))
 
(provide
  (contract-out
    (human strategy/c)
    (ai strategy/c)))
 
; - - - - - - - - - - -
; implementation
 
(define (general p)
  (lambda (_) pi))
 
(define (human x)
  ((general 'gui) x))
 
(define (ai x)
  ((general 'tra) x))

fast2

#lang racket
 
(define state? zero?)
(define action? odd?)
(define strategy/c
  (-> state? action?))
 
(provide
  (contract-out
    (human strategy/c)
    (ai strategy/c)))
 
; - - - - - - - - - - -
; implementation
 
(module no-contract racket
  (provide
    human
    ai)
 
  (define (general s)
    (lambda (_) pi))
 
  (define (human x)
    ((general 'gui) x))
 
  (define (ai x)
    ((general 'tra) x)))
 
(require 'no-contract)
The fast2 module on the right encapsulates the definitions in a submodule called no-contract; the provide in this submodule exports the exact same identifiers as the good2 module on the left. The main module requires the submodule immediately, making the identifiers available in the outer scope so that the contracted provide can re-export them.

While this second way of creating a no-contract submodule eliminates the run-time dependency on racket/contract, its compilation—as a part of the outer module—still depends on this library, which is problematic in a few remaining situations.

The third and last way to create a no-contract submodule is useful when contracts prevents a module from being used in a context where contracts aren’t available at all—neither at compile nor at run time. One example is racket/base; another is the contracts library itself. Again, you may wish you had the same library without contracts. For these cases, we recommend a file-based strategy one. Assuming the library is located at a/b/c, we recommend

  1. creating a c/ sub-directory with the file no-contract.rkt,

  2. placing the functionality into no-contract.rkt,

  3. adding (require "c/no-contract.rkt") to c.rkt, and

  4. exporting the functionality from there with contracts.

Once this arrangement is set up, a client module in a special context racket/base or for racket/contract can use (require a/b/c/no-contract). In a regular module, though, it would suffice to write (require a/b/c) and doing so would import contracted identifiers.

7.4 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.