% Dickinson User Guide % Vanessa McHale
Dickinson is a text-generation language for generative literature. Each time you run your code, you get back randomly generated text.
It provides a language to define random texts like the Magical Realism Bot or fortune program.
Distributions for some platforms are available on the releases page.
Un-tar the package, then:
make install
First, install cabal and GHC. Then:
cabal install language-dickinson
This provides emd
, the command-line interface to the Dickinson language.
You may also wish to install manpages for reference information about emd
.
Manpages are installed at
emd man
A vim plugin is available.
To install with vim-plug:
Plug 'vmchale/dickinson' , { 'rtp' : 'vim' }
To automatically enable spellchecking where appropriate put
autocmd BufNewFile,BufRead *.dck setlocal spell spelllang=en_us
in your ~/.vimrc
.
To configure Dickinson with exuberant ctags or
universal ctags, put the following in a file named
.ctags
:
--langdef=DICKINSON
--langmap=DICKINSON:.dck
--regex-DICKINSON=/:def *([[:lower:]][[:alnum:]]+)/\1/f,function/
--regex-DICKINSON=/tydecl *([[:lower:]][[:alnum:]]+) *=/\1/t,type/
I have the following in my ~/.vimrc
to keep tags updated:
augroup ctags
autocmd BufWritePost *.dck :silent !ctags -R .
augroup END
Dickinson files begin with %-
, followed by definitions.
Here is a simple Dickinson program:
%-
(:def main
(:oneof
(| "heads")
(| "tails")))
Save this as gambling.dck
. Then:
emd run gambling.dck
which will display either heads
or tails
.
The :oneof
construct selects one
of its branches with equal probability.
In general, when you emd run
code, you'll see the result of evaluating main
.
Comments are indicated with a ;
at the beginning of the line. Anything to the
right of the ;
is ignored. So
%-
; This returns one of 'heads' or 'tails'
(:def main
(:oneof
(| "heads")
(| "tails")))
is perfectly valid code and is functionally the same as the above.
We can define names and reference them later:
%-
(:def gambling
(:oneof
(| "heads")
(| "tails")))
(:def main
gambling)
We can emd run
this and it will give the same results as above.
When you use :oneof
, Dickinson picks one of the branches with equal
probability. If this is not what you want, you can use :branch
:
%-
(:def unfairCoin
(:branch
(| 1.0 "heads")
(| 1.1 "tails")))
(:def main
unfairCoin)
This will scale things so that picking "tails"
is a little more likely.
We can recombine past definitions via string interpolation:
%-
(:def adjective
(:oneof
(| "beautiful")
(| "auspicious")
(| "cold")))
(:def main
"What a ${adjective}, ${adjective} day!")
For large blocks of text, we can use multi-line strings.
(:def twain
'''
Truth is the most valuable thing we have — so let us economize it.
— Mark Twain
''')
Multiline strings begin and end with '''
.
Branches, strings, and interpolations are expressions. A :def
can attach
an expression to a name.
%-
(:def color
(:oneof
(| "yellow")
(| "blue")))
(:def adjective
(:oneof
(| "beautiful")
(| "auspicious")
(| color)))
(:def main
"What a ${adjective}, ${adjective} day!")
Branches can contain any expression, including names that have been defined
previously (such as color
in the example above).
Lambdas are how we introduce functions in Dickinson.
(:def sayHello
(:lambda name text
"Hello, ${name}."))
Note that we have to specify the type of name
- here, it stands in for some
string, so it is of type text
.
We can use sayHello
with $
(pronounced "apply").
(:def name
(:oneof
(| "Alice")
(| "Bob")))
(:def main
($ sayHello name))
We can emd run
this:
Hello, Bob.
$ f x
corresponds to f x
in ML.
Suppose we want to randomly pick quotes. First we define a function to return a quote by Fiona Apple:
(:def fionaAppleQuote
(:oneof
(|
'''
"You're more likely to get cut with a dull tool than a sharp one."
''')
(|
'''
"You forgot the difference between equanimity and passivity."
''')))
Then we can define quote
, which returns a quote as well as the person who said
it.
(:def quote
(:oneof
(| ("« Le beau est ce qu'on désire sans vouloir le manger. »", "Simone Weil"))
(| (fionaAppleQuote, "Fiona Apple"))))
Each branch returns a tuple.
We can use the :match
construct to format the result of quote
, viz.
(:def formatQuote
(:lambda q (text, text)
(:match q
[(quote, name)
'''
${quote}
— ${name}
'''])))
(:def main
$ formatQuote quote)
We can emd run
this:
"You forgot the difference between equanimity and passivity."
— Fiona Apple
Note the use of the :lambda
in formatQuote
; we specify the type (text, text)
.
Tags can be used to split things based on cases.
tydecl number = Singular | Plural
(:def indefiniteArticle
(:lambda n number
(:match n
[Singular "a"]
[Plural "some"])))
Note that we specify the type number
in (:lambda n number ...)
.
Tags themselves must begin with a capital letter while types begin with a lowercase letter.
Tags are a restricted form of sum types.
To enter a REPL:
emd repl
This will show a prompt
emd>
If we have
%-
(:def gambling
(:oneof
(| "heads")
(| "tails")))
in a file gambling.dck
as above, we can load it with
emd> :l gambling.dck
We can then evaluate gambling
if we like
emd> gambling
or manipulate names that are in scope like so:
emd> "The result of the coin toss is: ${gambling}"
We can also create new definitions:
emd> (:def announcer "RESULT: ${gambling}")
emd> announcer
Inspect the type of an expression with :type
:
emd> :type announcer
text
We can define types in the REPL:
emd> tydecl case = Nominative | Oblique | Possessive
emd> :type Nominative
case
We can save the REPL state, including any definitions we've declared during the session.
emd> :save replSt.emdi
If we exit the session we can restore the save definitions with
emd> :r replSt.emdi
emd> announcer
For reference information about the Dickinson REPL:
:help
Dickinson has several builtin functions. You can see all names in scope
(including builtins) with :list
, viz.
emd> :list
oulipo
allCaps
capitalize
titleCase
We can inspect the type like defined names:
emd> :type allCaps
(-> text text)
Try it out:
emd> $ allCaps "Guilt and self-laceration are indulgences"
GUILT AND SELF-LACERATION ARE INDULGENCES
emd
has a linter which can make suggestions based on
probable mistakes. We can invoke it with emd lint
:
emd lint silly.dck
Dickinson allows pulling in definitions from other files with :include
.
The color
module is bundled by default:
(:include color)
%-
(:def main
"Today's mood is ${color}")
Which gives:
Today's mood is citron
The :include
must come before the %-
; definitions come after the
%-
.
color.dck
contains:
%-
(:def color
(:oneof
(| "aubergine")
(| "cerulean")
(| "azure")
...
Upon encountering :include animals.mammal
, Dickinson looks for a file
animals/mammal.dck
.
When invoking emd
, we can use the --include
flag to add directories to
search.
Libraries can contain definitions and type declarations.
You can run emd check
on a library file to validate it.
emd
ignores any lines staring with #!
; put
#!/usr/bin/env emd
and the top of a file to use emd
as an interpreter. As an example, here is
an implementation of the Unix fortune program as a script:
#!/usr/bin/env emd
%-
(:def adjective
(:oneof
(| "good")
(| "bad")))
(:def main
"You will have a ${adjective} day")
Here is a variation on cowsay:
(:def cowsay
(:lambda txt text
'''
${txt}
------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
'''))
We can use tuples and tags to model nouns and noun declension.
tydecl case = Nominative | Accusative | Dative | Genitive | Instrumental
tydecl gender = Masculine | Feminine | Neuter
tydecl number = Singular | Plural
; demonstrative pronouns
; "this" or "these"
(:def decline
(:lambda x (case, gender, number)
(:match x
[(Nominative, Masculine, Singular) "þes"]
[(Accusative, Masculine, Singular) "þisne"]
[(Genitive, (Masculine|Neuter), Singular) "þisses"]
[(Dative, (Masculine|Neuter), Singular) "þissum"]
[(Instrumental, (Masculine|Neuter), Singular) "þys"]
[((Nominative|Accusative), Neuter, Singular) "þis"]
[(Nominative, Feminine, Singular) "þeos"]
[(Accusative, Feminine, Singular) "þas"]
[((Genitive|Dative|Instrumental), Feminine, Singular) "þisse"]
[((Nominative|Accusative), _, Plural) "þas"]
[(Genitive, _, Plural) "þissa"]
[(Dative, _, Plural) "þissum"]
)))
In the REPL:
emd> $ decline (Nominative, Feminine, Singular)
þeos
This actually has no element of randomness but such capabilities are important for agreement in longer generative texts.
For guidance:
emd> :type decline
(-> (case, gender, number) text)
This is a more sophisticated version of Maja Bäckvall's divination bot. The novelty is that by using tags, we get agreement between the Greek root and the definition.
%-
tydecl means = Fish
| Stars
| Snakes
| Sun
| Animals
| Lips
| Dreams
| Placenta
| Poo
| Fingers
| Number
...
(:def prefix
(:lambda x means
(:match x
[Fish "ichthyo"]
[Stars "astro"]
[Snakes "ophio"]
[Sun "helio"]
[Animals "zoo"]
[Lips "labio"]
[Dreams "oneiro"]
[Placenta "amnio"]
[Poo "scato"]
[Fingers "dactylo"]
[Number "numero"]
...
)))
(:def english
(:lambda x means
(:match x
[Fish "fish"]
[Stars "stars"]
[Birds "birds"]
[Snakes "snakes"]
[Sun "sun"]
[Animals "animals"]
[Lips "lips"]
[Dreams "dreams"]
[Placenta "placenta"]
[Poo "excrement"]
[Fingers "finger movements"]
[Number "numbers"]
...
)))
(:def means
(:pick means))
(:def postfix
(:branch
(| 1.0 "mancy")
(| 0.065 "scopy")
(| 0.03 "spication")
(| 0.06 "logy")))
(:def main
(:bind
[means means]
"${$prefix means}${postfix} - divination by ${$english means}"))
:pick
is a builtin construct which randomly selects a tag of type means
.
Note also :bind
in place of :let
--- this construct resolves all randomness
before bringing means
into scope.
So the Tracery bot might produce
uranospication
Divination using the appearance of proper names.
but ours produces results like
amniomancy - divination by placenta
We've also weighted postfix
so that the more common suffixes (such as '-mancy') occur more often.
See the full example in examples/divinationBot.dck
As an example, consider a Star Wars name generator:
%-
(:def main
(:let
[either
(:oneof
(| "Quarrel")
(| "Vult")
...
(| "Blot"))]
(:let
[firstname
(:oneof
(| "Yert")
(| "Wam")
(| "Pommet")
...
(| either))]
[lastname
(:oneof
(| "Grinell")
(| "Gorpax")
...
(| either))]
"${(:flatten firstname)} ${(:flatten lastname)}")))
The :flatten
builtin makes all child outcomes equally likely. So (:flatten firstname)
will sample "Quarrel", "Yert" with equal probability.
See the full example in examples/starwars.dck
Inspired by the Shakespeare Insult Kit's insult table, we can generate our own insults.
%-
(:def adjective
(:oneof
(| "artless")
(| "base-court")
(| "bawdy")
(| "bat-fowling")
...
(:def noun
(:oneof
(| "apple-john")
(| "baggage")
(| "barnacle")
(| "bladder")
...
(:def main
("Thou ${adjective} ${adjective} ${noun}!"))
Run it get something like:
Thou beslubbering clouted hedge-pig!
See the full example in examples/shakespeare.dck
.
Lyrics bots sample lyrics from some particular artist; see the africa by toto bot for an example.
We can make our own Fiona Apple bot, viz.
%-
(:def fiona
(:oneof
(| "You forgot the difference between equanimity and passivity.")
(| "You're more likely to get cut with a dull tool than a sharp one.")
(| "The child is gone.")
(|
'''
Oh darling, it's so sweet
You think you know how crazy, how crazy I am.
''')
...
(:def main
fiona)
See the full example in examples/fionaBot.dck
We can write our own magical realism bot using builtin libraries:
(:include profession)
(:include geography)
%-
(:def main
(:oneof
(|
(:let
[accomplishment
(:oneof
(|
(:let
[txt
(:oneof
(| "Excel spreadsheet")
(| "palimpsest"))]
[power
(:oneof
(| "comfort animals")
(| "practice bilocation"))]
(:oneof
(| "discovers a ${txt} that allows her to ${power}"))))
(|
(:let
[topic
(:oneof
(| "balneology")
(| "teleology")
(| "nephrology")
(| "orgonomy"))]
"writes a monograph on ${topic}"))
(|
(:let
[secret
(:oneof
(| "immortality")
(| "heliophagy")
(| "levitation")
(| "good skin"))]
"discovers the secret to ${secret}")
))]
"A ${profession} in ${bigCity} ${accomplishment}"))))
This reuses the bigCity
definition from the geography
library and profession
from the profesion
library.
This is not as sophisticated as the twitter bot but it is quite concise thanks to the libraries we used.