Skip to content
Go to file

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


A translation of Tracery into Haskell.


I wanted a Tracery library in Haskell, and I'm taking the opportunity to get to know the library at a deeper level by re-writing it as a from-scratch translation. The game is to achieve the same functionality as Tracery, but written and structured in a (my-)Haskell style.

Tracery is a text-expansion library for stochastic, generative text. An author provides a JSON formatted rule set (see /exampleGrammars for some samples) which is parsed and executed to produce one or more traces through the rule set (also called a grammar). Often a trace is flattened into a string, but an author also has the ability to work on the trace directly. A trace is a tree-structure of nodes that mark points of action or expansion in a run through the grammar.

To familiarize yourself with writing Tracery rule sets, please see the official Tracery tutorial. Any rule set that you author based on the guidance provided in the Tracery tutorial should run smoothly in this translation.

Basic Usage

Create a grammar from a Tracery-formatted object:

import Tracery

grammar :: Grammar
grammar = createGrammar "{\"origin\":\"foo\"}"

Add modifiers to a grammar. Tracery's baseEngModifiers are available,

-- a grammar with the baseEngModifiers
grammar :: Grammar
grammar = addModifiers (createGrammar "{\"origin\":\"foo\"}") baseEngModifiers

and you may also create your own sets of modifiers.

import qualified Data.Map.Strict as Map

-- a ModifierMap is a map from identifiers to (String -> String) functions
myModifiers :: ModifierMap
myModifiers = 
  Map.fromList [
    ("why", (\s -> "why " ++ s ++ "?"), 
    ("blorp", (++ "blorp blorp blorp"),
    ("noZs", (filter (not . (`elem` "zZ"))),
    ("length", (show . length))

-- ModifierMaps may be combined like normal Maps
-- (a union preferring the left map)
grammar :: Grammar
grammar = addModifiers someGrammar (myModifiers <> baseEngModifiers)

Once you have a grammar, the simplest thing to do with it is create a flattened trace. Because Tracery uses random selection in running a trace, some sort of random number generator (such as getStdGen) is required to run a trace.

import System.Random (getStdGen)

grammar :: Grammar
grammar = createGrammar "{ \"origin\":\"what a #adj# #time#\"\
                         \ \"adj\": [\"wild\", \"lovely\"]\
                         \ \"time\": [\"evening\", \"day\"]}"

runGrammar :: IO (String)
runGrammar = do
  gen <- getStdGen
  return $ createFlattened grammar gen
-- produces one of: "what a wild evening", "what a wild day",
--                  "what a lovely evening", "what a lovely day"

-- The above can also be written without do notation:
getStdGen >>= createFlattened grammar

A trace by default begins at the "origin" symbol. However, another symbol may be specified as the starting point for a trace through a grammar.

getStdGen >>= createFlattenedFromSymbol grammar "adj"
-- produces either "wild" or "lovely"

Working With Traces

Instead of generating a flattened string, you may also generate the intermediate trace object. Consult the documentation in Tracery.Grammar for more information on working with traces.

import Tracery.Grammar

-- expand returns both the expanded trace and the (possibly modified) number generator
trace :: RandomGen g => (Trace, g)
trace = expand someGrammar "origin" someGen

-- a trace may also be expanded incrementally
trace' :: RandomGen g => (Trace, g)
trace' = expandOne (newTrace (Lookup "origin") emptyCtx) someGrammar someGen

-- a trace (even one that is not fully expanded) may be flattened to a String
-- a grammar is required in both expansion and flattening
flattenedTrace :: String
flattenedTrace = flatten someGrammar someTrace


Parse rule sets and run traces in the terminal with tracery-cli. A rule set may be parsed from stdin, or you can read a rule set from a file.

Install tracery-cli

stack install haskery

Read the help text

tracery-cli --help

Example Grammars

The example grammars are a great place to get started. Some quick notes on their origins and purposes.

  • canonical.json: Tracery's classic example grammar. Generate a little story.
  • baseEngModifiers.json: a tour of the modifiers included in baseEngModifiers
  • pronouns.json: a demonstration of information setting. Set information for a character and reuse it across generation.
  • nesting.json: the nesting stories demonstration from the Tracery tutorial. Generate stories that recurse (with information setting too!).


an approximate translation of Tracery into Haskell




No packages published
You can’t perform that action at this time.