Skip to content

Instantly share code, notes, and snippets.

@sschwarzer
Last active October 16, 2022 18:00
Show Gist options
  • Save sschwarzer/5aaf9f203c552f1bf13a167dd08ded11 to your computer and use it in GitHub Desktop.
Save sschwarzer/5aaf9f203c552f1bf13a167dd08ded11 to your computer and use it in GitHub Desktop.
Racket tips and tricks

Racket tips and tricks

These are extracted from discussions I triggered on the Racket Slack. Thanks to everyone who participated in the discussion threads! :-)

Conversion to boolean

Use (and value #t) to convert a value to its boolean equivalent.

Entering a module in the Racket REPL

To enter a module in the REPL to access the non-provided symbols:

,enter my-module

If you have defined a test module inside my-module with

(module test+
  ...)

you can enter it with

,enter (submod my-module test)

To access individual identifiers, you can use require/expose from the module rackunit.

Creating struct accessors, but not the corresponding struct name

I wanted to create a struct similar to

(struct foo (x y))

with accessors foo-x and foo-y without also defining the name foo (because I wanted the name for a mapping from symbol to accessor, say, 'x -> foo-x).

The obvious

(struct foo (x y) #:constructor-name unused-foo)

doesn't work as intended because the name foo is still created for pattern matching transformers to be used at compile time.

Instead of the above code, use

(struct foo (x y) #:name unused-foo)

Another approach is

(struct foo (x y) #:omit-define-syntaxes)

Racket startup time

For many command line programs, the interpreter startup time can be much longer than the actual processing in the program. The startup time for

#lang racket/base

"Hello world"

executed with racket hello.rkt on my computer is 145 ms. Compilation with raco exe or raco make reduces the startup time to about 130 ms.

To reduce startup time, see Fast Racket. However, this doesn't bring the startup time below the mentioned 130 ms.

Most of the interpreter startup time is from loading "linklets". The command

$ PLT_LINKLET_TIMES=1  racket -l racket

shows how Racket spends the time when loading linklets. An example output is

;; on-demand                0 [0] ms ; 21 times
;; read-linklet            12 [3] ms ; 283 times
;; run                     22 [5] ms
;;   instantiate           22 [5] ms ; 1255 times
;; read                    51 [0] ms
;;   read-bundle            0 [0] ms
;;   faslin-literals       14 [0] ms ; 1216 times
;;   faslin-code           37 [0] ms ; 1216 times
;; boot                    73 [0] ms
;; total                  158 [8] ms
;;
;; faslin-code              5 MB

"faslin" means "fasl in", see Fast Load Serialization.

Running tests with raco test

On some occasions, I didn't get proper stacktraces when running

$ raco exe file-to-test.rkt

i. e. in some cases the stacktrace didn't include the frames where the failure (say, a contract violation) actually occurred.

One (somewhat verbose) approach is

$ racket -l racket/init -l errortrace -e '(require (submod "file.rkt" test))'

Another approach is to use the test-e command, provided by this package.

Defining contracts for structs

I thought that you could add a contract to a struct constructor like to any regular function. For example, in one file I had

(provide
  (contract-out
    [sort-spec (-> symbol? symbol? sort-spec?)]
    ...)
  sort-spec?
  value-sort-func)

However, this resulted in an error:

task-group.rkt:20:24: struct: parent struct type not defined;
 identifier does not name struct type information
  at: sort-spec
  in: (struct task-group-spec sort-spec (tag-key) #:transparent)
  context...:
   /.../collects/racket/private/define-struct.rkt:153:2
   /.../collects/syntax/wrap-modbeg.rkt:46:4

There are different workarounds:

  • Use struct/contract, but this doesn't have all customizations that struct has. Another downside is that the contract affects the whole module, not just the module boundary, so this approach may come with a performance degradation.
  • Provide the contract with the #:guard keyword argument for struct. As before, this condition is enforced for all instances, whether in the defining module or not.
  • Use [struct my-struct ...] inside contract-out, see Contracts on structures. This applies only to the module boundary, but it implies that all "functions" of the struct are exported, in particular the accessors. This is a problem if such accessors should be kept private.
  • Not using a contract for the struct construction at all.

Make explicit that a name is private to a module

There's no common idiom for this, but my approach my-name/private was considered good. :-)

Omitting the function name from an error message

In a module, I have

(with-handlers ([exn:fail? (lambda (exn) (eprintf "~a\n" (exn-message exn)))])
  (main))

An error message from that might look like

open-input-file: cannot open input file
  path: /home/.../todoreport/crd,desc
  system error: No such file or directory; errno=2

However, if the message is intended for end users, the function name isn't relevant.

It seems that the caught exception object doesn't have fields for the error number, the error reason etc., only the message itself. So the only workaround is using text operations to strip the "function-name: " from the error message.

An alternative to with-handlers is setting the error-display-handler parameter. This example also contains the code to strip the function name:

(let ([this-handler (error-display-handler)])
  (error-display-handler
   (lambda (msg exn)
     (this-handler
      (regexp-replace #px"^.*?: " msg "")
      exn))))

Using different code for different operating systems

Put the code for the respective platform in its own module and use dynamic-require. To actually distinguish the platforms, use system-type.

Wrapping a paragraph of text

To wrap a paragraph of text like Python's textwrap module, use wrap-line from scribble/text/wrap. This function isn't documented in Scribble form, but well-documented in the code. At least it's not in a private directory that would clearly suggest not using the function.

As an alternative, wrap the text "manually". This sounds odd, but I asked for using the function to format command line help, and it turned out it was better to insert the line breaks manually anyway.

Creating packages

Package management in Racket has a lot of information on packaging, but it's easy to miss the forest for the trees.

Also raco setup is useful for some things.

There's a tutorial for creating single-collection packages.

See the lazytree package for an example of a multi-collection package.

raco pkg new

$ raco pkg new <your-package-name>

creates a skeleton directory structure (with a few files) for a new package your-package-name.

raco setup

(not raco pkg setup)

Before using raco setup, the package has to be installed.

$ raco setup --fix-pkg-deps --pkgs <your-package-name>`

adds missing dependencies to the info.rkt file.

raco setup can also be used with the module name/path, say:

$ raco setup file/todo-txt

Reinstalling a package

A package can be reinstalled by using

$ raco pkg remove <package-name>

and

$ raco pkg install ../<working-dir>

but it's shorter to use

$ raco setup --check-pkg-deps --pkgs <package-name>

once the package has been initially installed.

info.rkt

Many possibilities of the info.rkt file(s) are documented in Controlling raco setup with "info.rkt" Files and "info.rkt" file format.

Installing binaries is possible with racket-launcher-names and racket-launcher-libraries in the info.rkt. Example:

(define racket-launcher-names '("todoreport"))
(define racket-launcher-libraries '("todoreport.rkt"))

where the paths in racket-launcher-libraries is relative to the info.rkt file the forms appear in. It's not possible to use relative paths that go up in the directory hierarchy, say, "../todoreport.rkt".

You can have different info.rkt files in the directory tree for the package. Here's an example of a package of mine:

$ tree
├── file
│   ├── info.rkt [1]
│   ├── todoreport
│   │   ├── info.rkt [2]
│   │   └── private
│   │       └── cli.rkt
│   ├── todoreport.rkt
│   ├── todo-txt
│   │   ├── doc
│   │   │   ├── ...
│   │   │   └── todo-txt
│   │   │       └── ...
│   │   ├── info.rkt [3]
│   │   ├── private
│   │   │   ├── task-group.rkt
│   │   │   └── task.rkt
│   │   └── scribblings
│   │       └── todo-txt.scrbl
│   └── todo-txt.rkt
├── info.rkt [4]
├── LICENSE
└── ...

As you can see, there are four info.rkt files:

  1. This contains the launcher definition as shown above.
  2. This only contains (define collection "todoreport")
  3. This is similar to 2, but also contains a definition for the documentation under scribblings.
  4. This file contains most of the package definition, in particular the line (define collection 'multi), the dependencies, the build dependencies, a package description and the package version. This file is adapted from the file that is generated by raco pkg new.

raco docs

$ raco docs <your-module>

for example

$ raco docs file/todo-txt

opens the generated documentation for an already installed package.

Reusing contracts from modules in Scribble documentation

The "official" way to achieve this is to add information for Scribble to the contract-out section of module provides. Here's a blog article.

Bogdan Popa created a module that should be able to extract the contract information without changes to the module where the functions or structs are defined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment