On this page:
1.1 About Clotho
1.2 Getting Started
1.2.1 Installation
1.2.2 Enabling Clotho in Your Code
1.3 The Basics of Clotho
1.3.1 The current-random-source Parameter
1.3.2 Extracting the Byte String
1.3.3 Replaying Randomness with Changes
1.4 Advanced Clotho Usage
1.4.1 Using Externally Defined Randomness Functions
1.4.2 Abstracting Regions of Randomness
1.4.3 Easy Stateful Execution
1.4.4 Automatically Wrapping a Whole Module
8.12

1 How to Use Clotho🔗ℹ

Pierce Darragh <pierce.darragh@gmail.com>,
William Gallard Hatch <william@hatch.uno>,
and Eric Eide <eeide@cs.utah.edu>

Clotho is a library that provides new randomness primitives that enable the parameterization of random value generation.

1.1 About Clotho🔗ℹ

Clotho is a library for supporting parametric randomness. Parametric randomness is a type of random value generation that allows you to write programs that use random values, record those values, manipulate them externally, and then use them to guide a subsequent execution of your program.

In addition to support for parametric randomness, Clotho provides almost all of the randomness functionality of the Racket standard library—meaning you can use Clotho any time you want random values.

Clotho also provides the ability to embed externally defined functions (such as functions that depend on using Racket’s built-in randomness functions). This allows for using parametric randomness with practically any existing library, which is good news for you, since you won’t have to re-implement them from scratch.

1.2 Getting Started🔗ℹ

In this section, we go over the basics of Clotho.

1.2.1 Installation🔗ℹ

To install Clotho, you need a modern version of Racket. (We recommend version 7.0 or later.) Use Racket’s built-in raco tool to install Clotho:

$ raco pkg install clotho

This guide shows verbatim code blocks such as the one immediately above this note to indicate interactions on the command line. Within these blocks, a leading $ indicates a line the user could type at a shell, and a leading > indicates a line the user could type within a Racket REPL session. Lines that do not begin with either of these symbols represent output.

1.2.2 Enabling Clotho in Your Code🔗ℹ

A Clotho program will typically start like this:

#lang clotho

But you can also do:

#lang racket
 
(require clotho)

Throughout this guide, we will predominantly be using Clotho as a #lang, but the examples should mostly work identically if you instead require it.

1.3 The Basics of Clotho🔗ℹ

Clotho provides a set of randomness primitives and convenience functions that serve two purposes:

  1. Replace randomness functionality of racket/base.

  2. Enable parametric randomness.

Let’s learn about how to use Clotho to achieve these two goals.

1.3.1 The current-random-source Parameter🔗ℹ

Crucially, all of Clotho’s functionality revolves around the current-random-source parameter.

If you do not know about parameters in Racket, we recommend you take a moment to consult the documentation about them.

The current-random-source must be parameterized with a new random-source?, which can easily be constructed by calling make-random-source. The make-random-source function is fairly versatile. You can supply it with:

The randomness primitives and convenience functions will only work where the current-random-source is parameterized. For example:

"success.rkt"

#lang clotho
 
(parameterize ([current-random-source (make-random-source)])
  (println (random)))

All of the code samples in the Clotho guide are available in the Clotho repository, under the clotho/examples/guide directory. For example, the above code sample can be found in the clotho/examples/guide/success.rkt file.

$ racket success.rkt

0.9193169593852777

Outside of the body of such a parameterize expression, an error will be thrown instead:

"error.rkt"

#lang clotho
 
(println (random))

$ racket error.rkt

assert-random-source-initialized: current-random-source not yet initialized!

  Have you `parameterize`d it?

1.3.2 Extracting the Byte String🔗ℹ

After calling some randomness functions, we can analyze the recording of the generation that was captured in the current-random-source. This is a byte string (bytes?), which can be obtained by calling the get-current-random-source-byte-string function:

"byte-string.rkt"

#lang clotho
 
(define-values
  (rv bs)
  (parameterize ([current-random-source (make-random-source)])
    (values
     (if (random-bool)
         (random-int)
         (random-bool))
     (get-current-random-source-byte-string))))
 
(println rv)
(println bs)
(println (bytes->list bs))

$ racket byte-string.rkt

-518059394

#";\240)`\355\325\334\354\31\344\310\244"

'(59 160 41 96 237 213 220 236 25 228 200 164)

From this output, we know the first call to random-bool produced #t, and then the call to random-int produced -518059394.

The byte string captured in bs consists of 4-byte chunks that each represent an integer. The first integer is reserved for internal use. Each 4-byte integer after that corresponds directly to the output of a randomness function. Since there were two calls to randomness functions, we see 12 bytes in the byte string: 4 for internal use, and then 2 x 4 = 8 bytes devoted to randomness calls.

1.3.3 Replaying Randomness with Changes🔗ℹ

What makes Clotho neat is the ability to run a random program a second time and replay the values obtained the first time. For example, we can rewrite the above program to use the previously generated byte string, and then execute it:

"replay.rkt"

#lang clotho
 
(define inbs #";\240)`\355\325\334\354\31\344\310\244")
 
(define-values
  (rv bs)
  (parameterize ([current-random-source (make-random-source inbs)])
    (values
     (if (random-bool)
         (random-int)
         (random-bool))
     (get-current-random-source-byte-string))))
 
(println rv)
(println bs)
(println (bytes->list bs))

$ racket replay.rkt

-518059394

#";\240)`\355\325\334\354\31\344\310\244"

'(59 160 41 96 237 213 220 236 25 228 200 164)

By providing the byte string as an argument to make-random-source, we create a new random-source? that will produce the same randomness values we got out previously.

This isn’t very exciting, though. What would be really cool is if we could manipulate the source of randomness to induce subtle changes in the generated values. As you probably guessed by the way that last sentence was written, we can!

Let’s manipulate the integer that corresponds to the call that produced #t in the previous program, and then feed the manipulated byte string into the same program to see what happens:

"manipulate.rkt"

#lang clotho
 
(require racket/list)
 
;; This is the byte string we got before.
(define prevbs #";\240)`\355\325\334\354\31\344\310\244")
 
(define inbs
  (list->bytes
   (list-update (bytes->list prevbs)
                7
                add1)))
 
(println inbs)
(println (bytes->list inbs))
 
(define-values
  (rv bs)
  (parameterize ([current-random-source (make-random-source inbs)])
    (values
     (if (random-bool)
         (random-int)
         (random-bool))
     (get-current-random-source-byte-string))))
 
(println rv)
(println bs)
(println (bytes->list bs))

$ racket manipulate.rkt

#";\240)`\355\325\334\355\31\344\310\244"

'(59 160 41 96 237 213 220 237 25 228 200 164)

#t

#";\240)`\355\325\334\355\31\344\310\244"

'(59 160 41 96 237 213 220 237 25 228 200 164)

We can see that we incremented the seventh byte by 1, which we expect to modify the value of the first randomness function. After the generation is complete, we get back the same byte string we passed in. This is because the byte string was sufficiently long to handle all of the needed randomness calls.

It is because each of these 4-byte segments can be handled separately from one another that we say Clotho provides mechanisms for parametric randomness. Essentially, each 4-byte segment becomes a parameter that can be adjusted.

The output we get out is the value #t, instead of the number -518059394 that we had received previously. This shows that the first randomness call (the call to random-bool in the condition of the if expression) output a #f instead of the #t we were getting before.

Note that the third tetrad of bytes in the byte string (i.e., bytes 8–11) did not change, but produced a boolean instead of an integer value. This is because Clotho’s byte strings do not record values directly, but rather record numbers that are seeds to individual random generators that are used for producing values for each randomness call. Although this reduces Clotho’s efficiency somewhat, it increases the expressive power considerably.

1.4 Advanced Clotho Usage🔗ℹ

There are some additional aspects of Clotho’s design that can be very helpful to know, but that are somewhat more advanced than what we’ve covered so far.

1.4.1 Using Externally Defined Randomness Functions🔗ℹ

Sometimes, you may find yourself wanting to use a function somebody else has written that uses Racket’s built-in randomness functions. This could be problematic if you want calls to this function to be recorded in the current-random-source’s byte string.

Clotho provides a form, wrap-external-randomness, that allows you to achieve exactly this. Consider this example using the shuffle function provided by the racket/list library:

"external.rkt"

#lang clotho
 
(require racket/list)
 
(define numbers (range 10))
 
(define-values
  (rv1 bs1)
  (parameterize ([current-random-source (make-random-source 0)])
    (values
     (shuffle numbers)
     (get-current-random-source-byte-string))))
 
(define-values
  (rv2 bs2)
  (parameterize ([current-random-source (make-random-source 0)])
    (values
     (wrap-external-randomness
      (shuffle numbers))
     (get-current-random-source-byte-string))))
 
(println rv1)
(println bs1)
(println (bytes->list bs1))
(displayln "")  ;; Insert a newline for easier reading.
(println rv2)
(println bs2)
(println (bytes->list bs2))

Then, we run the code twice:

$ racket external.rkt

'(4 9 2 5 0 8 7 6 3 1)

#"\0\0\0\0"

'(0 0 0 0)

 

'(5 7 0 2 9 6 8 1 3 4)

#"\0\0\0\0\347\240n\333"

'(0 0 0 0 231 160 110 219)

 

$ racket external.rkt

'(0 2 5 1 4 3 9 8 6 7)

#"\0\0\0\0"

'(0 0 0 0)

 

'(5 7 0 2 9 6 8 1 3 4)

#"\0\0\0\0\347\240n\333"

'(0 0 0 0 231 160 110 219)

There are a few things to notice about the outputs here.

First, the return values rv1 and rv2 are not the same within each execution. Second, the rv2 values are identical between executions, but the rv1 values are not. Third, the byte strings labeled bs1 are shorter than the byte strings labeled bs2.

The takeaway here is that the wrap-external-randomness form has caused the calls to shufflewhich is not implemented with Clotho’s randomness functions—to become constrained by the current-random-source.

The rv1 values in each execution are unpredictable, despite occurring within the parameterization of the current-random-source. In most cases, this would probably be an undesirable effect. In contrast, the rv2 values are correctly handled by the current-random-source. In both executions, rv2 and bs2 come back the same. Additionally, we see that the byte string increased in length. This is because Clotho generated and recorded a randomness value associated with the wrapped call to shuffle, which did not happen in the call that wasn’t wrapped with wrap-external-randomness.

It is important to point out that Clotho is unable to wrap each individual call to a randomness function within the scope of a wrap-external-randomness form. Rather, the execution of the entire form is associated with exactly one 4-byte segment of the byte string. To put it in code:

"external-multiple.rkt"

#lang clotho
 
(require racket/list)
 
(define numbers (range 10))
 
(define
  bs1
  (parameterize ([current-random-source (make-random-source 0)])
    (wrap-external-randomness
     (shuffle numbers)
     (shuffle numbers))
    (get-current-random-source-byte-string)))
 
(define (wrapped-shuffle . args)
  (wrap-external-randomness
   (apply shuffle args)))
 
(define
  bs2
  (parameterize ([current-random-source (make-random-source 0)])
    (wrapped-shuffle numbers)
    (wrapped-shuffle numbers)
    (get-current-random-source-byte-string)))
 
(println bs1)
(println bs2)

$ racket external-multiple.rkt

#"\0\0\0\0\347\240n\333"

#"\0\0\0\0\347\240n\333l\331\317\250"

The first byte string, bs1, shows that only one 4-byte segment was created for the two calls to random. On the other hand, bs2 is 4 bytes longer than bs1. This is because each call to wrapped-shuffle uses its own wrap-external-randomness form. The advantage of this is that each randomness call is now parameterized (in the Clotho sense of the word).

In most cases, it will be more advantageous to opt for very tight scoping on usages of wrap-external-randomness. This requires either wrapping each individual call to a randomness function, or else defining a new wrapper function to handle the wrapping more easily.

1.4.2 Abstracting Regions of Randomness🔗ℹ

It can occasionally be useful to abstract complex uses of randomness functions. For example, you may not be interested in parameterizing each randomness function, but rather a broader-scoped meta-randomness function:

"racket-abstract.rkt"

#lang clotho
 
(define (random-list-of-vals)
  (list (random-int)
        (random-bool)
        (random-ascii-lower-char)))
 
(define (abstracted-random-list-of-vals)
  (with-new-random-source
    (list (random-int)
          (random-bool)
          (random-ascii-lower-char))))
 
(define-values
  (rv1 bs1)
  (parameterize ([current-random-source (make-random-source 0)])
    (values
     (random-list-of-vals)
     (get-current-random-source-byte-string))))
 
 
(define-values
  (rv2 bs2)
  (parameterize ([current-random-source (make-random-source 0)])
    (values
     (abstracted-random-list-of-vals)
     (get-current-random-source-byte-string))))
 
(println rv1)
(println bs1)
(println (bytes->list bs1))
(displayln "")
(println rv2)
(println bs2)
(println (bytes->list bs2))

$ racket abstract.rkt

'(-826043031 #t #\g)

#"\0\0\0\0\347\240n\333l\331\317\250.\313\2454"

'(0 0 0 0 231 160 110 219 108 217 207 168 46 203 165 52)

 

'(-1378798560 #f #\h)

#"\0\0\0\0\347\240n\333"

'(0 0 0 0 231 160 110 219)

We can quickly observe that the return values rv1 and rv2 are different. More interesting in the present discussion, however, is the difference in lengths of the byte strings bs1 and bs2. There are 16 bytes in bs1, versus only 8 bytes in bs2. This tells us that bs1 corresponds to three randomness function calls, while bs2 corresponds to only one.

The with-new-random-source form used in abstracted-random-list-of-vals makes it so that all of the Clotho randomness functions called within its body will correspond to a single 4-byte segment of the output byte string.

The with-new-random-source form is a macro that automatically creates a new current-random-source seeded with the value returned from calling random-seed-value within the context of the parent current-random-source. This means that if you call get-current-random-source-byte-string within the body of the with-new-random-source form, it will return the byte string of the child current-random-sourcewhich is probably not what you intended. Be careful where you call get-current-random-source-byte-string!

1.4.3 Easy Stateful Execution🔗ℹ

There are times when you may want to use randomness functions without having to use a cumbersome parameterize expression. For example, if you are interacting with the Racket REPL to test some functionality, it can be frustrating to wrap each call in this way. To accommodate this, you can (require clotho/stateful):

$ racket

Welcome to Racket v7.4.

> (require clotho)

> (random)

; assert-random-source-initialized: current-random-source not yet initialized!

;   Have you `parameterize`d it? [,bt for context]

> (require clotho/stateful)

> (random)

0.3642047298500743

There are various forms to support the use of this mode documented in the Clotho API. Note also that clotho/stateful can be used as a #lang:

#lang clotho/stateful

1.4.4 Automatically Wrapping a Whole Module🔗ℹ

When you wish to use an external randomness function from a given module, you can use Clotho’s wrap-external-randomness macro to wrap the function call in such a way that the internal randomness will be guided by Clotho like any other randomness call in Clotho. However, this can be cumbersome to use if you wish to wrap many functions within a library.

To address this issue, Clotho provides the require-clotho-wrapped macro that can be used in a require position. This macro will automatically wrap every Phase 0 binding within that module with wrap-external-randomness.

For example, we can wrap the math/distributions library since it provides quite a few randomness functions, and it’s reasonable to want to use many of them at once:

"math-distributions.rkt"

#lang clotho
 
(require-clotho-wrapped math/distributions #:provide? #t)
 
(define-values
  (rv1 bs1)
  (parameterize ([current-random-source
                  (make-random-source)])
    (values
     (let ([d (normal-dist)])
       (sample d 3))
     (get-current-random-source-byte-string))))
 
(define-values
  (rv2 bs2)
  (parameterize ([current-random-source
                  (make-random-source bs1)])
    (values
     (let ([d (normal-dist)])
       (sample d 3))
     (get-current-random-source-byte-string))))
 
(println rv1)
(println bs1)
(println (bytes->list bs1))
(displayln "")
(println rv2)
(println bs2)
(println (bytes->list bs2))

$ racket math-distributions.rkt

'(-1.4422649979023663 0.170764610196471 -0.014599159848523677)

#"\360\343+g\356\344\207\225\217\307_\325"

'(240 227 43 103 238 228 135 149 143 199 95 213)

 

'(-1.4422649979023663 0.170764610196471 -0.014599159848523677)

#"\360\343+g\356\344\207\225\217\307_\325"

'(240 227 43 103 238 228 135 149 143 199 95 213)