On this page:
2.1 Semantics
rule-struct
weight?
rule-evaluate
backtrack
exn:  backtrack
make-rule-parameter
current-clause-selector
default-clause-selector
clause-selector/  c
2.2 Syntax
rule
define-rule
simple-rule
define-simple-rule
~>
2.3 Preconditions
need
once
n-times
2.4 Sequences
cycle
cycle/  repeat-last
8.12

2 The jen Reference🔗ℹ

 (require jen) package: jen-lib
jen reprovides all of the modules in this package.

2.1 Semantics🔗ℹ

 (require jen/base) package: jen-lib
jen/base provides the low-level semantics and tools for working with rules and clauses.

struct

(struct rule-struct (clauses))

  clauses : (listof (cons/c (-> any/c) (-> weight?)))
The rule-struct structure represents procedural generation rules, the central concept of this library.

clauses is an association list where each pair represents a clause that may be randomly selected. The key is the try thunk, a nullary procedure that’s applied when the clause is selected; and the value is the weight thunk, also a nullary procedure, which is applied to determine the clause’s likelihood of being selected (see weight?).

Applying a rule-struct value is the same as applying rule-evaluate to it.

procedure

(weight? a-value)  boolean?

  a-value : any/c
Recognizes weights, which are numbers used to describe the relative likelihood of a clause being selected. A weight is rational? (including integers), non-negative?, and exact?.

procedure

(rule-evaluate a-rule-struct    
  [#:default default-value])  any/c
  a-rule-struct : rule-struct?
  default-value : any/c = an opaque value
Evaluates a rule as follows:

The rule’s clauses’ weight thunks are all evaluated, then try thunks are selected according to (current-clause-selector) and evaluated until one of them succeeds, i.e., doesn’t backtrack. (See backtrack and exn:backtrack.) If a clause succeeds, rule-evaluate returns the result of its try thunk. If no clause succeeds, then the entire rule backtracks.

The probability of a clause being chosen is its computed weight divided by the total of all clauses’ computed weights.

If a #:default argument is provided, it will be the return value in case the rule would have otherwise backtracked. This is useful for keeping the backtrack signal from bubbling to the top when a rule is being evaluated outside of any other rule. (See exn:backtrack for how to deal with it in general.)

procedure

(backtrack [message])  none/c

  message : string? = #f
Signals a backtrack by raising an exn:backtrack value.

If message is provided, it will be used to provide a more descriptive error message for debugging purposes.

exn:backtrack represents the backtrack signal.

Although usually unnecessary (see rule-evaluate), it’s possible to catch a backtrack manually by installing an exn:backtrack? handler. For example:

(define-rule start)
 
(with-handlers ((exn:backtrack? (const "It backtracked!")))
  (start))

"It backtracked!"

procedure

(make-rule-parameter [initial-value])  parameter?

  initial-value : any/c = #f
Makes a rule parameter, which, whenever a clause backtracks, reverts its value to whatever it was just before the clause was tried.

This is useful for tagging rules with additional state that needs to remain consistent with respect to clauses being tried but failing.

parameter

(current-clause-selector)  clause-selector/c

(current-clause-selector clause-selector)  void?
  clause-selector : clause-selector/c
A parameter that decides which clause to try whenever one is called for by rule-evaluate. Its value is a procedure, called a clause selector, that accepts two arguments: the total weight of all clauses currently being considered, and an association list of said clauses. The total weight is a natural? and the procedure must produce a natural? strictly less than that one. The total weight is guaranteed not to be 0. (See clause-selector/c for the exact specification of the contract.)

Note that the total weight and the individual weights of the clauses passed as arguments may have been multiplied by a shared factor to guaranteed that they’re natural numbers. The relationship that a clause’s likelihood of being selected equals its weight divided by the total weight is preserved.

Clause selectors may feel free to ignore their second argument; it’s provided for when extra introspection is desired, but a "fair" selector most likely has no use for it.

The default clause selector implements a "fair" random selection; see default-clause-selector for specifics.

Beware that a rule that is well behaved with the default clause selector may not terminate if a different one were to be used. For example:

(define-rule very-small
  (~> "very " (very-small))
  (~> "small"))

This will not terminate if the selector always produces 0.

The default value of current-clause-selector. It produces (random 0 total-weight), where total-weight is its first argument. Because it uses random, its output can be controlled with current-pseudo-random-generator.

The contract that applies to the value of current-clause-selector. It’s defined to be:

(->i ([total-weight natural?]
      [_ (listof (cons/c (-> any/c) natural?))])
     [_ (total-weight) (and/c natural? (</c total-weight))])

2.2 Syntax🔗ℹ

 (require jen/syntax) package: jen-lib
jen/syntax provides clean syntax on top of the rule primitives to make it easier to work with them in common cases.

syntax

(rule clause ...)

 
clause = proc-expr maybe-weight
     
maybe-weight = 
  | #:weight weight-expr
Produces a rule whose clauses are given by clauses.

For each clause, proc-expr is evaluated immediately to obtain the clause’s try thunk, while weight-expr (by default, 1) becomes the body of the weight thunk and thus is evaluated only when the rule is.

syntax

(define-rule id rest ...)

Shorthand for (define id (rule rest ...)).

syntax

(simple-rule expr ...)

Shorthand for (rule (thunk expr) ...).

This is useful when each of your clauses only needs to be a single expression without complex logic, allowing you to omit ~> or other procedure-producing forms.

syntax

(define-simple-rule id rest ...)

Shorthand for (define id (simple-rule rest ...)).

syntax

(~> expr ... maybe-combiner)

 
maybe-combiner = 
  | #:combiner combiner-expr
Returns a procedure taking no arguments and returning the result of applying combiner-expr (by default, ~a) to each expr, but filtering out any values satisfying void?. This is meant for use with rule or define-rule but produces ordinary procedures, much like thunk but slightly more specialized.

The void?-filtering behavior is particularly useful alongside jen/preconditions, which contains procedures and forms that affect clause evaluation and return (void). If this turns out to be undesirable for a given use case, it’s still possible to use thunk with define-rule.

2.3 Preconditions🔗ℹ

jen/preconditions provides support for clause preconditions, by which a clause can decide not to execute if its conditions aren’t met.

procedure

(need condition)  void?

  condition : any/c
Returns a void? value when condition is true; otherwise signals a backtrack by calling (backtrack).

syntax

(once)

Shorthand for (n-times 1).

syntax

(n-times n)

 
  n : natural?
During the current top-level rule evaluation, if the clause containing the call site of this form has previously been committed to less than n times, the form evaluates to a void? value. Otherwise, it signals a backtrack (see backtrack).

The upshot of this is that the clause containing the call to this macro can be allowed to be committed to at most n times before it always fails.

Underlyingly, each call site of this macro uses its own rule parameter (see make-rule-parameter) as a counter of how many times it’s been reached (and its clause committed to).

2.4 Sequences🔗ℹ

jen/sequential contains forms for non-random, ordered evaluation that nonetheless depends on the current rule state.

syntax

(cycle expr ...+)

Cycles through the exprs, returning the value of one each time this form is evaluated as part of a clause. If the clause has never been committed to before, the expression will be the first one; if it has been committed to once, it will be the second one; and so on. Once all expressions have been exhausted, it begins anew with the first one.

syntax

(cycle/repeat-last expr ...+)

Like cycle, but once all expressions have been exhausted, it repeats the last one.