# Ten Years Building a Symbolic Language Engine The short version of this story is that I made a rhyming dictionary. That is the version that fits on a line, and it is the version that survived, and it is wrong in the specific way that a photograph of a building's front door is wrong about the building. What I actually spent the decade beginning in 1994 and extending into the mid-2000s doing was constructing several different [[wiki/Knowledge Representation|computable representations]] of the English language and then building an instrument that let a writer move between them without ever stopping to look anything up. Rhyme was one of those representations. It was the deepest one, and the one that turned out to be marketable, and so it became the name of the thing. But the object I was building was not a dictionary. It was a **traversal engine over language**, and the dictionary was one of its published surfaces. In contemporary terms, the architecture crosses [[wiki/Computational Linguistics|computational linguistics]], [[wiki/Natural Language Processing|natural language processing]], [[wiki/Information Retrieval|information retrieval]], [[wiki/GOFAI (Good Old-Fashioned AI)|symbolic AI]], and [[wiki/Artificial Intelligence|artificial intelligence]]. The project-specific name for the integrated architecture is [[wiki/Symbolic Language Engine|Symbolic Language Engine]]. That is the premise of the story. Everything that follows shows how the representations, indexes, corpora, interfaces, and publishing systems became one coherent language architecture. --- ## What this architecture would be called now - **Multiple computable representations describe the same lexical universe.** Modern translation: [[wiki/Knowledge Representation|knowledge representation]], multi-view data modeling, and [[wiki/Multi-Index Language Representation|multi-index representation]]. - **Words, pronunciations, meanings, phrases, triggers, and examples have stable addresses.** Modern translation: canonical identifiers, [[wiki/Resource Addressing|resource addressing]], and a lexical symbol table. - **Separate rhyme, Soundex, synonym, trigger, anagram, and compound indexes answer different questions.** Modern translation: specialized retrieval indexes and [[wiki/Hybrid Retrieval|hybrid retrieval]]. - **A Perl pipeline converts raw corpora into `MASTER` datasets.** Modern translation: [[wiki/Corpus Engineering|corpus engineering]], ETL, and an offline feature pipeline. - **Expensive analysis happens before the user asks a question.** Modern translation: [[wiki/Offline Precomputation|offline precomputation]], precomputed features, and [[wiki/Materialized View|materialized views]]. - **Runtime containers provide keyed, sequential, vector, and hierarchical access.** Modern translation: [[wiki/Access Geometry|access geometry]], in-memory indexes, and retrieval-optimized data structures. - **Associations are traversed as weighted relations rather than synonym lists.** Modern translation: a [[wiki/Lexical Knowledge Graph|lexical knowledge graph]], [[wiki/Semantic Network|semantic network]], and distributional association graph. - **Concept sets emerge from observed usage and proximity.** Modern translation: [[wiki/Distributional Semantics|distributional semantics]], graph neighborhoods, and corpus-derived semantic fields. - **Several primary concepts are expanded and intersected.** Modern translation: multi-hop retrieval, graph traversal, and contextual candidate generation. - **Pronunciation is stored separately from spelling, with multiple pronunciations per word.** Modern translation: a structured [[wiki/Pronunciation Lexicon|pronunciation lexicon]], phonological representation, and lexical ambiguity modeling. - **Long words rhyme against multiword phrases across token boundaries.** Modern translation: sequence alignment, approximate phonological matching, and variable segmentation. - **Meaning, rhyme, meter, grammar, and attestation constrain generation together.** Modern translation: [[wiki/Constraint Satisfaction|constraint satisfaction]], constrained decoding, and [[wiki/Semantic-First Constrained Generation|semantic-first generation]]. - **One canonical derived core feeds software, books, HTML, and wiki files.** Modern translation: [[wiki/Output Projection|output projection]], [[wiki/Derivative Asset Pipeline|derivative asset pipeline]], and multi-channel publishing. - **The editor continuously updates relevant lexical and prosodic context.** Modern translation: context-aware IDE behavior, incremental computation, and a [[wiki/Linguistically Instrumented Editor|linguistically instrumented editor]]. - **Symbolic indexes can be combined with embeddings and model suggestions.** Modern translation: [[wiki/Neuro-Symbolic AI|neuro-symbolic AI]], [[wiki/Semantic Retrieval|semantic retrieval]], and retrieval-augmented generation. - **Every suggestion can preserve the reason it was retrieved.** Modern translation: explainable AI, source attribution, and [[wiki/Provenance-Sensitive Multiplicity|provenance-sensitive results]]. The compact translation is: **this was a symbolic and corpus-derived language platform combining computational linguistics, ETL, specialized indexes, a lexical knowledge graph, hybrid retrieval, incremental editor instrumentation, and multi-constraint generation.** Neural embeddings can extend it today, but they do not make its explicit phonological, taxonomic, attested, and provenance-bearing representations obsolete. The association graph is analogous to a modern knowledge graph, and the indexed lexical representations can participate in RAG. They remain explicit symbolic structures whose edges and retrieval rules can be inspected, complementing the learned geometry of neural embeddings. See [[wiki/Modern Technical Translation Map|Modern Technical Translation Map]] for the shared vocabulary. ## I. Language as a thing with an address I have never experienced being a writer and being a programmer as two occupations. They are one disposition applied to one object. Language is a system with structure that can be addressed, indexed, transformed, and operated upon, and the fact that it is also the medium in which human beings say what they mean has never seemed to me like an argument against treating it as infrastructure. It has seemed like the reason to. I was running a BBS at ten. That matters here less as a credential than as a formative fact about what I thought a computer was for. A bulletin board is a text system. Everything that happens on it is language moving through storage and being retrieved by other people, and the whole craft of running one is the craft of organizing text so that it can be found. By the time I was doing anything that would be recognized as computational linguistics, I had already spent a decade thinking of writing as a [[wiki/Information Retrieval|storage and retrieval problem]] in addition to an expressive one. The consequence, which shows up everywhere in what follows, is that I never approached a language question by asking what the answer was. I approached it by asking **what representation would make the question cheap**. That is a programmer's reflex applied to a writer's material, and it is the single most useful thing I brought to the work. --- ## II. Building the workshop first Before any of the language work makes sense, there is a prior fact that explains how one person sustained it: I built my own tools first, and then I never rebuilt them. Somewhere around the turn of the century I had accumulated a personal Perl standard library I called [[wiki/Shazam Perl Library|shazam]]. It is roughly two hundred and fifty modules, one function per file, organized under a fixed prefix taxonomy — `io_`, `string_`, `struct_`, `data_`, `net_`, `image_`, `datetime_`, `utils_`, `keydb_`, `cgi_` — with a generator script that concatenated them into a single importable `shazam-complete.pm`. Function names like `io_flatten_for_storage`, `string_extract_unique_words_and_numbers`, `struct_return_array_unique_items`, and `string_hash_cruncher_strict_alphanum_only` are not glamorous, and they are precisely the layer that made everything else possible. Every corpus pipeline I wrote for the next several years was **composition over primitives I had already written once**. CPAN existed. I wanted the primitives under my own hand, in a naming scheme I could hold in memory, with behavior I had personally decided. Underneath that sat [[wiki/KeyDB|KeyDB]], which was my own storage format and the module that read and wrote it. The convention is almost embarrassingly simple: one key-value record per line, pipe as the field separator, and — because linguistic data immediately destroys any naive flat format by containing the very characters you chose as delimiters — an escaping layer that encoded newlines and literal pipes as bracketed placeholders on the way in and reversed them on the way out. Around that I built exclusive and shared file locking, merge-and-write, overwrite, delete-key, delete-all-records-matching, increment and decrement, unique key assignment, bulk search-and-replace across keys or values, HTML escaping, and a parallel binary path using Perl's `Storable` for the cases where parse time mattered more than being able to read the file in a text editor. It is an **embedded key-value store with a full CRUD surface**, written because installing and administering a database engine was overhead I did not want in a program that had to run off a thumb drive. The error-handling section of that module carries an annotation in my own hand: _Modified: Bryant — 4.27.2001_. It is an internal timestamp embedded in the artifact itself. That workshop made a decade of corpus engineering possible. The plumbing was built once and reused, so each new project became composition over an accumulating set of known primitives. The through-line I described in the previous section — language as infrastructure — applied reflexively. I treated my own tooling the same way I treated English: as a system worth indexing properly once. --- ## III. The pipeline, and what was actually being built The language and dictionary work began in **1994**, while I was still in the Triad Center period in Utah. In **1996**, after Triad, I worked heavily on VersePerfect and the natural-language projects in Spring City, using Delphi and other Borland tools before my Borland employment. The tools and the conversations I had with people in the Borland developer community while advancing the program later helped lead—somewhat incidentally—to my being hired after I moved to Scotts Valley. The project continued as a major side project while I worked there and then through roughly a decade of extension, correction, corpus growth, and productization. What that decade mostly consisted of, in terms of hours, was [[wiki/Corpus Engineering|data engineering]]. This is the part that gets omitted when the project is described as a dictionary, and it is the part where most of the intellectual work lived. The shape of it is recoverable from the surviving directory structure, because I named things in a way that encodes the pipeline. Third-party raw material is filed under `wordlist <source>` — `wordlist cmudict`, `wordlist wordnet`, `wordlist moby mwords`, `wordlist 12dicts`, `wordlist lexical freenet old`. Those sources included the [[wiki/CMU Pronouncing Dictionary|CMU Pronouncing Dictionary]], [[wiki/WordNet|WordNet]], and [[wiki/Lexical FreeNet|Lexical FreeNet]]. Canonical derived products are filed as `MASTER <THING>`. Runnable pipeline drivers get a leading exclamation point so they sort to the top and announce themselves as the things you execute rather than the things you import: `!Clean Strange Chars In Files.pl`, `!File Clean.pl`, `!Create Translation Dirs.pl`, `!Generate KeyDB Database and Indexes.pl`, `!Build MASTER ANAGRAMS.pl`. The polarity of the whole operation is written into the filenames. **Ingredients on one side, products on the other, and the scripts that convert between them clearly marked.** One surviving driver, `process.pl`, is a good specimen of what the middle of that pipeline looked like. It loads fourteen source files through the shazam library, combines them, counts the result, and then applies a sequence of explicit eligibility rules: **keep entries containing spaces** — because the target was multiword material, not dictionary headwords — reject hyphenated entries, reject a particular leading pattern, drop anything under six characters, deduplicate the remainder through a hash, and write the survivors out. The commented-out sections are as informative as the live code, because they show stopword removal and sorting being tried and set aside. That restraint was deliberate: sophisticated rhyme phrases derive much of their usable sound structure from exactly the function words that ordinary information retrieval discards. _In the night_, _out of sight_, _for a while_ — strip the stopwords and you have destroyed the thing you were collecting. Those fourteen source files encode their own record counts in their filenames, and they sum to approximately **6.35 million source records**. The output of that filtering was a normalized wordlist of about eleven megabytes. Nine hundred thousand usable phrase-level rhyme candidates emerged from a stated curation policy applied to six and a third million candidates. The `MASTER` inventory itself is the most compact description of the project that exists. There is `MASTER RHYMES` and `MASTER RHYMES INDEX`. There is `MASTER RHYMES COMPOUND`. There is `MASTER SOUNDEX`. There is `MASTER SYNONYMS` and `MASTER SYNONYMS INDEX`. There is `MASTER TRIGGERS`. There is `MASTER ANAGRAMS`. Together they form [[wiki/Multi-Index Language Representation|six or seven distinct derived representations built over an overlapping lexical universe]], each optimized for a different question you might ask about the same words: what sounds like this, what approximately sounds like this, what means something adjacent to this, what stands in a taxonomic relation to this, what tends to occur near this in real human language, what can be spelled from this. The same pipeline fed several different consumers, which is the other structural fact that the filenames give away. Alongside the scripts that generated the KeyDB databases and indexes for the application, there are scripts that generated static HTML and scripts that generated wiki files. The **fourteen-volume printed dictionary was a rendering target of the pipeline**, not a parallel lexicographic undertaking. One canonical derived core, several published surfaces — the Windows application, the books, the web pages. That is why a single person could produce fourteen volumes and a piece of software at the same time. They were the same artifact typeset differently. ![[resources/images/book-cover-rhyme-v1-450w-front.jpg]] --- ## IV. The phonological engine Here is the problem that started it, stated the way I actually experienced it rather than the way it appears in a specification. I wanted a five-syllable word to rhyme against a phrase. Not against another five-syllable word — there usually is not one, and when there is, it is usually wrong. Against a **phrase**, because that is what the ear does. The ear does not know where the spaces are. It hears a sequence of sounds, and if a run of three short words produces the same terminal sound pattern as one long word, then they rhyme, and every songwriter and every rapper knows this and no rhyming dictionary I could buy would do it. Stated as a computational problem, that is [[wiki/Approximate Phonological Matching|approximate phonological sequence matching]] across [[wiki/Lexical Segmentation|variable lexical segmentation]]. The target is a phoneme sequence. The candidate space is a corpus of nine hundred thousand multiword phrases. A match may distribute the target's sound across two, three, or four lexical tokens on the candidate side. Word boundaries are not the unit of the problem; [[wiki/Phonological Representation|the phonological sequence is the unit of the problem]], and lexical segmentation is a property of the candidate rather than a constraint on the search. And when no exact alignment exists — which is most of the time, once the target is long — the system must not return nothing. It must degrade intelligently toward near rhyme, which means rhyme cannot be a binary predicate. It has to be a [[wiki/Approximate Phonological Matching|graded correspondence]] with a [[wiki/Candidate Generation and Ranking|ranking]], sensitive to how much of the sequence aligns and where the alignment sits relative to the stressed nucleus. The system represented **rhyme as a continuum of alignment quality** rather than as membership in a fixed equivalence class. The phonological representation becomes visible in a single screenshot. Look up _dove_ and the program returns two pronunciations as first-class tabs, each producing a disjoint rhyme set. One yields _above, glove, love, shove, thereof_. The other yields _clove, cove, grove, jove, rove, stove, strove, trove_. _Dove_ is a [[wiki/Phonological Representation|heteronym]]: the bird and the past tense of _dive_ are different words that happen to be spelled alike. The system represents a word as one or more pronunciations, derives rhyme classes from those pronunciations, and carries pronunciation multiplicity through retrieval and into the interface so the writer can choose which word they meant. The status bar says it plainly: _2 pronunciations for 'dove'_. ![[resources/images/verse-perfect-dual-pronunciation-rhymes.jpg]] > **Figure.** Two pronunciations of _dove_, each with its own rhyme set. For this screen to be possible, the lexicon must store words as sets of pronunciations, derive rhyme classes phonologically rather than orthographically, and carry that multiplicity through retrieval into the interface. The phrase layer is where the multiword work becomes visible. Clicking _More_ opens the **Phrases & Proper Nouns** bank, and for the _dove/love/stove_ neighborhood it returns hundreds of entries — _Cottage Grove_, _Council Grove_, _Copperas Cove_, _bird of Jove_, _franklin stove_, _potbellied stove_, _kerosene stove_, _cook stove_. Real writing does not terminate at dictionary headwords. Titles, place names, personal names, and idiomatic multiword expressions all have phonological endings, and they are frequently the only thing that will actually land. Supporting them meant the retrieval model had to extend past a conventional word lexicon into a **multi-token lexical layer**, which is exactly what the six-million-record filtering pipeline in the previous section was manufacturing. ![[resources/images/verse-perfect-phrases-proper-nouns.jpg]] > **Figure.** The Phrases & Proper Nouns bank. Supporting these requires a multi-token lexical layer with its own phonological indexing — a conventional headword lexicon cannot produce them. There is a subsystem called **Roundex** whose output occupies its own panel and extends the phonetic neighborhood around a word. It let the engine move outward from exact rhyme toward increasingly approximate relationships. ![[resources/images/verse-perfect-split-panel-workspace.jpg]] > **Figure.** Split-panel display with Standard Rhymes, Context, Verse Forms, the editor, the Rhyme Lines corpus, Phrases & Proper Nouns, and the Roundex panel all live simultaneously. The [[wiki/Prosody and Syllabification|syllable counter]] deserves more respect than it usually gets, because it looks trivial and is not. English syllable count is not reliably derivable from character patterns — silent vowels, diphthongs, contractions, possessives, abbreviations, proper names, and unknown words all defeat the obvious heuristics. With a [[wiki/Pronunciation Lexicon|pronunciation lexicon]] you can count vowel nuclei for most words, but any production system needs defensible fallback behavior for lexical gaps, because writers type words that are not in your dictionary constantly. And because the count updates while you type, the editor has to couple document-change events to [[wiki/Corpus Engineering|tokenization]] and phonological lookup without perceptible delay. The engineering problem is not _count the syllables_. It is maintain [[wiki/Prosody and Syllabification|prosodic metadata]] as an interactive invariant of a changing text. ![[resources/images/verse-perfect-live-syllable-counting.jpg]] > **Figure.** Per-line syllable counts updating as you type. Every keystroke has to be routed to tokenization, pronunciation lookup, and a partial recount without perceptible latency. --- ## V. The instrument The design thesis of [[wiki/VersePerfect|VersePerfect]] fits in one sentence that appears, more or less, on one of its own promotional screenshots: **no more interrupting your creative workflow to look up words**. That sentence names the thing I was actually trying to destroy. Reference software of that era assumed a transaction. Stop writing. Decide which resource you need. Formulate a query. Open the resource. Read the answer. Return to the document. Reacquire your place, your intention, and your momentum, all of which have degraded. The [[wiki/Linguistically Instrumented Editor|lookup transaction]] is a tax on composition, and the tax is not the seconds — it is the eviction of whatever you were holding in your head. So the system was built to infer the object of assistance from the editor state and to keep the relevant linguistic possibilities standing around the act of writing. The current token drives the rhyme results. The selected pronunciation determines which rhyme neighborhood is showing. Line edits update syllable counts. Structural position updates the verse-form annotation. Semantic context populates synonyms. And a literary corpus panel surfaces attested lines underneath all of it. Nothing is requested. The document is not passive text; typing modifies a [[wiki/Linguistically Instrumented Editor|live computational state]], and the machine's job is to keep a continuously updated linguistic model of the document and of where the writer's attention currently is. ![[resources/images/verse-perfect-main-editor.jpg]] > **Figure.** The main editor. Line numbers, red syllable counts, and green rhyme-class letters run down the gutter while the right-hand panels track the current token's pronunciations, rhymes, semantic context, and verse form — all simultaneously, all derived from editor state. ![[resources/images/verse-perfect-ambient-rhyme-synonyms.jpg]] > **Figure.** The design thesis, stated on the software's own promotional annotation: rhymes and synonyms arrive as you type, so the lookup transaction never happens. [[wiki/Formal Grammar|Verse forms]] were the part where I stopped treating a poetic structure as explanatory text and started treating it as data. Selecting _Italian Sonnet_ projects the form into the document as a column of rhyme-class letters running beside the lines, synchronized with the syllable counts in the adjacent column. The program holds a [[wiki/Knowledge Representation|formal representation capable of assigning expected structural positions to successive lines]] and displays those expectations against what you have written. That is a small domain-specific grammar for poetic form, and the selector held dozens of them — Rime Royal, Rubaiyat, Sestina, Shakespearean and Spenserian sonnets, the Spenserian stanza, Tanka, Terza Rima, Villanelle, and several quatrain variants. ![[resources/images/verse-perfect-verse-form-projection.jpg]] > **Figure.** The selected verse form projected into the document as a rhyme-class column beside the lines, synchronized with syllable counts. The form is stored as structure, not prose. ![[resources/images/verse-perfect-verse-form-selector.jpg]] > **Figure.** Dozens of classical and contemporary forms in the selector. Each entry implies a stored structural definition the editor can evaluate line positions against. Each form also carried human-readable description, instruction, and a worked example. Choosing _Haiku_ and opening the information window gives the definition, the seasonal convention, and an example with its 5/7/5 counts displayed — an example, as it happens, that I wrote. Pedagogy and constraint checking in the same object, which is a thing I would still argue for. ![[resources/images/verse-perfect-verse-form-information.jpg]] > **Figure.** The verse-form information window: definition, instruction, and a worked example with syllable counts. Forms carried metadata, not just structure. [[wiki/Information Retrieval|Rhyme Lines]] was the corpus feature, and conceptually it is a different discipline from everything else in the program. Underneath the editor, continuously, the software surfaces complete lines from published poetry that relate to the current lexical and phonological context, with title and author attached — Tennyson's _All Things Will Die_ returning its hammering series of _must die_ lines, _The Lady of Shalott_, Dickinson, Sexton, Barrett Browning. This required ten thousand poems normalized, associated with authorship metadata, and annotated with rhyme data so that a retrieval key could connect the writer's current target to candidate lines fast enough to be ambient rather than requested. It is a specialized concordance operating **at the point of composition**, and its cognitive function is straightforward: not _give me a word_, but _show me how this language has actually been used by people who were good at it_. ![[resources/images/verse-perfect-rhyme-lines-corpus.jpg]] > **Figure.** Rhyme Lines returning attested usage from published poetry with author and title. Behind it: ten thousand poems normalized, metadata-associated, and rhyme-annotated for retrieval. The [[wiki/Semantic Network|Hyperbolic Thesaurus]] rendered lexical relations as an explorable graph rather than a list — _love_ at the center, with _pleasure_, _gratification_, and _enjoyment_ as primary neighbors and their own descendants radiating outward, node coloring distinguishing focal state from depth. I put that visualization **inside a working editor**, beside phonology, corpus search, and formal verse constraint, so that a writer could move from a word to its sound neighbors, semantic neighbors, or taxonomic superordinates without changing applications or losing the document. ![[resources/images/verse-perfect-hyperbolic-thesaurus.jpg]] > **Figure.** The Hyperbolic Thesaurus. Lexical relations rendered as traversable topology rather than an alphabetical list. The [[wiki/Word Sense Disambiguation|Word Explorer]] was the textual counterpart to that graph, and it is where the [[wiki/Ontology|ontology work]] is most legible. Look up _illusion_ and it returns the word partitioned into [[wiki/Word Sense Disambiguation|numbered senses]], with buttons switching between noun, verb, adjective, and adverb readings, and relational tabs labeled _Nearest_, _Synonyms_, _Type of_, _Types_, and _See also_. Those last two are the ones that matter. _Type of_ and _Types_ are [[wiki/Lexical Semantics|hypernym and hyponym traversal]] — taxonomic relations, not flat synonymy — which means the semantic layer was operating on sense-disambiguated lexical relations rather than on a bag of similar words. ![[resources/images/verse-perfect-word-explorer-illusion.jpg]] > **Figure.** Word Explorer on _illusion_: numbered senses, part-of-speech switching, and typed relations including hypernym and hyponym traversal. ![[resources/images/verse-perfect-word-explorer-feel.jpg]] > **Figure.** Word Explorer on _feel_, showing the same sense-partitioned, relation-typed structure. The [[wiki/DICT Protocol|Online Lookup]] subsystem federated external reference material through one interface. The controls are a `Database:` selector and a `Search Strategy:` selector, and the database list is long enough to be its own argument: the Collaborative International Dictionary of English, WordNet 2.0, Grady Ward's Moby Thesaurus II, an elements database, the Jargon File, the Free On-line Dictionary of Computing, Easton's and Hitchcock's Bible dictionaries, Bouvier's Law Dictionary, _The Devil's Dictionary_, the CIA World Factbook, the U.S. Gazetteers down to ZIP code tabulation areas, and then a long run of bilingual Freedict resources — Latin, Dutch, Portuguese, Scottish, Serbo-Croat, Slovenian, Spanish, Swahili, Swedish, and Turkish. The interface follows the [[wiki/DICT Protocol|DICT protocol]] pattern standardized as RFC 2229 in 1997: a **common query abstraction over heterogeneous lexical resources and languages**, so the writer never had to learn each backend. ![[resources/images/verse-perfect-online-dictionary-lookup.jpg]] > **Figure.** Online Lookup returning a formatted dictionary entry inside the application, with Database and Search Strategy as first-class controls. ![[resources/images/verse-perfect-reference-database-selector.jpg]] > **Figure.** The database selector: dozens of heterogeneous reference resources exposed through one query abstraction. ![[resources/images/verse-perfect-multilingual-dictionary-resources.jpg]] > **Figure.** The bilingual resources continue for another screen, turning an English poetry tool into a federated multilingual lexical client. Spell checking sat on the same event stream, and I built the checker rather than delegating it, because the point was that **every linguistic subsystem should share one lexical universe**. A misspelling gets the wave underline and a context menu of corrections, exactly as it would in a word processor — but in this program the same keystroke that triggered the spelling check also updated the syllable count, the rhyme neighborhood, the verse-form annotation, the semantic panel, and the corpus retrieval. That convergence is what made it a **linguistically instrumented editor** rather than a dictionary with a text box attached. ![[resources/images/verse-perfect-live-spell-checking.jpg]] > **Figure.** Live spell checking. The same document-change event feeds spelling, syllabification, rhyme, verse structure, semantics, and corpus retrieval simultaneously. Tabs for Wikipedia, Wiktionary, and Dictionary.com rendered directly inside the application, with the Rhyme Lines panel still alive underneath. The interaction architecture treated the emerging web knowledge commons as **one more context surface inside a domain-specific creative workstation**, subject to the same rule as everything else: the writer does not leave the document. ![[resources/images/verse-perfect-embedded-wikipedia.jpg]] > **Figure.** Wikipedia rendered inside the writing environment, with the corpus panel still live beneath it. Another context surface, same rule: never leave the document. Finally, the dictionary existed as its own application, which is the screenshot that clarifies the relationship between the two products. The window titled [[wiki/McGill English Dictionary of Rhyme|McGill English Dictionary of Rhyme 2.0]] has its own tab structure — Combined, Side by Side, Roundex, Online Dictionary, Rhyme Lines, Hyperbolic Thesaurus — with Pronunciation 1 and Pronunciation 2 as first-class navigation and the Combined view merging standard rhymes with the phrase bank into one result set. VersePerfect did not have a small rhyme list bolted onto a word processor. **VersePerfect grew around an already substantial lexical engine.** ![[resources/images/verse-perfect-rhyme-dictionary-application.jpg]] > **Figure.** The dictionary as its own application, with pronunciation tabs and its own view modes. The editor was built around this engine, not the other way around. --- ## VI. What it was actually for Everything above describes a system that assists a human writer. The research program underneath it was asking a harder question, and this is the part of the work I care most about preserving. It began with grammar. I had been building increasingly elaborate Chomsky-style generative bots, and they taught me the limitation of [[wiki/Formal Grammar|formal grammar]] directly rather than theoretically: **a sentence can be perfectly well formed and have nowhere to go**. Grammaticality gives you admissible structures. It does not give you a thought. If a machine is going to produce language that develops rather than merely continues, something has to supply the trajectory, and syntax cannot. So I went looking for the trajectory in corpora, and what I found were [[wiki/Trigger-Based Language Modeling|trigger relations]] — pairs of terms where the appearance of one changes the relevance of the other somewhere in the surrounding discourse, each carrying a small numeric weight. These are association pairs rather than synonyms or simple adjacent bigrams: _Galileo_ and _Jupiter_. _Hunting_ and _handgun_. _Selling_ and _stocks_. Alongside them is the messier texture of actual speech: _yes_ and _great_, _yep_ and _uh_, _she_ and _like_. Sentence-boundary tokens survive in the data because this material originated in [[wiki/Language Model|statistical language modeling]]. Source files named `!swb_t.txt` and `!bn_t.txt` contain the conversational and broadcast textures associated with [[wiki/Switchboard Corpus|Switchboard]] and [[wiki/Broadcast News Corpus|Broadcast News]]. The technology those triggers came out of was built to answer a narrow question — **which word becomes more probable given that another word occurred**. I was asking a much broader one. If linguistic association is treated as a **traversable structure**, can a machine follow human conceptual movement through language? That reframing is the whole of [[wiki/Living Language — Proximal Frequency Research Reference|_Living Language: Proximal Frequency Research Reference_]], and the name is literal rather than decorative. **Proximal** meant proximity in observed language treated as evidence about conceptual relationship. Starting from a term, you follow associations outward into other regions of the corpus, and you weight the connections by combining [[wiki/Distributional Semantics|global occurrence]], [[wiki/Distributional Semantics|local occurrence]], and [[wiki/Distributional Semantics|degree of proximity]]. Global occurrence gives the background rate, so you can discount a term that connects to everything. Local occurrence tells you whether a relation is unusually concentrated in the neighborhood you are currently traversing. Proximity tells you how tightly the two things actually co-occur. Together they let you distinguish a ubiquitous weak relationship from a rare but sharply localized one, which is the entire difference between noise and signal in association data. That produces a concept set rather than a synset. A [[wiki/Synset vs Concept Set|synset]] asks which lexical items participate in a particular already-classified sense. A [[wiki/Synset vs Concept Set|concept set]] is a region of language that becomes visible by traversing repeated associations, recovered from usage rather than assigned by a lexicographer. _Ocean_ leads beyond synonyms toward water, waves, coast, ships, depth, salt, horizon, distance, voyage, drowning, storms, memory, and isolation. These are places human language actually goes from _ocean_. When an author supplies several primary terms — say _ocean_, _mother_, and _loss_ — the interesting region is where the three conceptual fields **begin to interact**, which is exactly where a poem might have something to say. That is what the generator was for. The architecture I was assembling worked in this direction: the author establishes the subject by supplying primary concepts; the associative machinery expands those into weighted neighborhoods and traverses promising paths while maintaining relationship to the original subject and recent context; grammatical machinery produces candidate constructions; the pronunciation, rhyme, phrase-alignment, syllable, and verse-form systems constrain which realizations are admissible; and then the spelling checker and a rudimentary grammar checker I also wrote inspect the surface that actually came out, after all the other constraint systems have finished interfering with each other, and pull it back toward acceptable written English. The name I would give that is [[wiki/Semantic-First Constrained Generation|semantic-first constrained generation]], and the ordering is the argument. Most procedural poetry runs the other way: pick a rhyme, generate something syntactically plausible that arrives at it, repeat, and produce lines that scan and mean nothing. I wanted the **developing thought to come first** and rhyme to be a constraint on its expression. Which is also, incidentally, the real reason the multiword phrase work mattered. It was never only a better lookup for human poets. If a concept has to resolve into a particular sound and the system is not restricted to single dictionary words — if it can realize that sound across a phrase — then the **semantic engine has vastly more freedom to preserve the meaning while satisfying the constraint**. Nine hundred thousand phrases is not a bigger rhyme list. It is a bigger solution space for an inverse problem. Every layer interferes with every other layer. The perfect rhyme destroys the meaning. The precise word destroys the meter. The natural construction goes nowhere conceptually. Solving one constraint reliably degrades three others. That is a [[wiki/Constraint Satisfaction|constraint-satisfaction problem over multiple simultaneous representations of language]], and I was trying to solve it before there was convenient machinery for expressing that kind of problem at all. Part of that research came back out as a physical object. [[wiki/Poet's Muse|_Poet's Muse: Associative Reference for Writer's Block_]] was typeset and produced as a print-on-demand book directly from the trigger databases — a corpus-derived association graph flattened onto paper, so that a writer stuck on a page could open it and be shown where language actually goes from where they are. ![[resources/images/poets-muse-cover 1.jpg]] The work drew from several active lineages in the field. Doug Beeferman's [[wiki/Lexical FreeNet|Lexical FreeNet]] combined rhyme relations with semantic relations and appears in my source directories; the precursor to [[wiki/RhymeZone and Datamuse|RhymeZone]] dates to early 1996. I used [[wiki/WordNet|WordNet]]. My trigger data descends from [[wiki/Trigger-Based Language Modeling|trigger-based language modeling]] published out of Carnegie Mellon in 1993. Visual semantic-network products existed by the late 1990s. I took representations that lived in separate research communities, datasets, command-line tools, and products and **synthesized them into one low-latency instrument**, then pushed past assistance toward generation. The distinctive object was the integrated architecture. --- ## VII. Publication, performance, and the architecture that endured The application shipped widely. Linux.com carried an item in December 2004 identifying me as editor and author of the fourteen-volume dictionary and describing the electronic release. In 2008, Softpedia's Madalina Boboc reviewed VersePerfect 2.0 with an editorial five out of five after exercising rhyme and definition search, import and export, the verse editor, syllable counting, spell checking, thesaurus suggestions, Word Explorer, Hyperbolic Thesaurus, and multi-database lookup, while also measuring responsiveness, CPU use, and memory consumption. Professor Joseph Pisano, then Director of Music and Fine Arts Technology at Grove City College, wrote a substantial endorsement of it as a classroom tool. My promotional material reported more than five hundred thousand users. The software also accumulated twenty-six award badges from download portals, part of the texture of software distribution in 2007. The performance the reviewer measured is the direct consequence of the architecture described in section three, and I want to close the technical argument on it. VersePerfect ran from a folder. No installer, no dependency chain, no database engine, no configuration ceremony — unzip it to a thumb drive, run it, and get full-speed access to the entire language environment on whatever machine was in front of you. That was possible because **the expensive thinking had already been done**. The application did not discover relationships in language at query time; it consumed the output of years of offline preprocessing, organized into indexes and containers engineered for cheap retrieval. The intelligence was distributed across the human-designed representation, the pipeline that built it, the indexes that shaped it, and only lastly the runtime algorithms. That is why it felt instantaneous on hardware that had no business feeling instantaneous. The application itself was a Delphi program, and its surviving component inventory tells a consistent story: an embedded local database for persistent structured storage, full-text indexing components for the corpus material, Perl-compatible regular expression machinery inside the compiled application, rich editor controls, a spelling engine, local HTML rendering, network components for the dictionary services, and archive components consistent with a product distributed as a self-contained zip. The piece of that inventory that most directly explains the responsiveness is a container library called **DIContainers**, which I was using from the late 1990s and which my working Delphi tree retained in both source and Delphi 7 build form. DIContainers was a **generic container framework for a version of Pascal that had no generics**, and its central design decision was to **separate the structure of a container from the type of the data inside it**. Items were memory-efficient Pascal records managed through handler objects responsible for initialization, finalization, copying, comparison, and streaming. Define a record and a handler and you could put an arbitrary application-specific structure — a pronunciation record, a phrase record, a corpus reference, a weighted association — inside any of the library's topologies. Those topologies were **hash tables, doubly-linked lists, N-ary trees, and vectors**, with assignment between representations so the same logical items could move between physical organizations. That is the second half of the two-stage architecture. The Perl pipeline manufactured **already-reduced computational representations** offline; DIContainers supplied the **access geometry** into which those representations were materialized at runtime — keyed retrieval where the question was _what maps to this_, indexed vectors where the question was _scan this precomputed candidate set_, hierarchies where the question was taxonomic, ordered chains where the question was sequential. Expensive work upstream, cheap geometry downstream. DIContainers offered no native graph type, so a graph was **built out of the primitives** — a hash of identifiers whose records carry vectors of weighted outgoing edges. That is the shape the associative work required, and it allowed the graph to exist as an implicit structure long before I stored one in something that called itself a graph database. The containers were in the runtime from the beginning. **The two-stage architecture was the original design**: offline manufacture of reduced representations followed by runtime materialization into fast access geometry. One present-tense fact, because everything else here is written in the past tense about software nobody can run. The pipe-delimited key-value format I wrote in Perl in 2001 for the dictionary pipeline is still serving my website today. **Twenty-five years, several languages, one storage convention, and the thing it stores is still language.** If it were rebuilt, the temptation would be to make it the same thing on the web, and I think that would undersell what was actually discovered. The object worth resurrecting is not the feature list. It is the **linguistic workspace architecture** — a document surrounded by continuously updated representations of its sound, its meaning, its structure, its precedent, and its external knowledge. [[wiki/Word Embeddings|Embeddings]], [[wiki/Machine Learning|neural pronunciation models]], local [[wiki/Language Model|language models]], and [[wiki/Vector Retrieval|vector retrieval]] would extend that enormously, and they should sit downstream of the original idea rather than replacing it. The [[wiki/Neuro-Symbolic AI|symbolic layers]] would in fact be an asset now rather than a limitation, because they can make distinctions that generative systems blur. A rebuilt system could tell you, of any suggestion it offers, exactly what kind of thing it is: **this one is a phonological match; this one is a semantic relation; this one is a taxonomic relation; this one is attested in a corpus; this one satisfies the verse form you selected; this one is only a model's guess.** [[wiki/Provenance-Sensitive Multiplicity|Provenance-sensitive multiplicity]]. That would make it a considerably more serious instrument than a generic machine that writes poems for you, and it is what I would build if I built it again. The description I would leave behind, then, is not _he made a rhyming dictionary_. It is this: from 1994 into the mid-2000s I built a [[wiki/Symbolic Language Engine|symbolic language engine]] — a hand-engineered, corpus-derived, [[wiki/Multi-Index Language Representation|multi-index model of English]] spanning [[wiki/Phonological Representation|phonology]], [[wiki/Approximate Phonological Matching|approximate phonology]], [[wiki/Lexical Semantics|lexical semantics]], [[wiki/Ontology|ontological relation]], [[wiki/Distributional Semantics|distributional association]], and literary precedent — and an instrument that let a writer traverse all of it without leaving the page, and then I spent the back half of that decade trying to make the machine traverse it on its own. The dictionary was the surface that got published. The engine was the work. --- ## References - Related project: [[projects/Dual-Render Authorship and the Atomic Markdown Double-Brace Grammar|Dual-Render Authorship: The Atomic Markdown Double-Brace Grammar]] — the later application of multi-representation language engineering to the author's own sentences. - [[wiki/WordNet|WordNet — Princeton University]] - [[wiki/CMU Pronouncing Dictionary|CMU Pronouncing Dictionary]] - [[wiki/DICT Protocol|RFC 2229: A Dictionary Server Protocol]] - [[wiki/RhymeZone and Datamuse|Datamuse — origins of the Semantic Rhyming Dictionary and RhymeZone]] - [Thinkmap Visual Thesaurus](https://www.visualthesaurus.com/aboutus/) - [[wiki/Trigger-Based Language Modeling|Roni Rosenfeld — publications on trigger-based language modeling]] - [[wiki/Switchboard Corpus|Switchboard-1 Release 2, Linguistic Data Consortium]] - [[wiki/Broadcast News Corpus|Broadcast News transcripts, Linguistic Data Consortium]] - [Wikipedia — Wikimedia Foundation](https://wikimediafoundation.org/) - [DIContainers — Yunqa, The Delphi Inspiration](https://www.yunqa.de/delphi/products/containers/index) - [DIContainers: Version History](https://www.yunqa.de/delphi/products/containers/history) - Linux.com, "McGill English Dictionary of Rhyme," December 12, 2004 - Madalina Boboc, review of VersePerfect 2.0, Softpedia, 2008 - Joseph Pisano, review of VersePerfect, _MusTech.Net_, April 2007 ![[resources/images/vp-logo-500w.jpg]]