Plurality.Rules (Plurality v0.3.0)

Copy Markdown View Source

Suffix rule engine using last-byte dispatch for English noun inflection.

This module transforms words by their suffix when they are not found in the uncountables set or irregulars map (Tiers 1 and 2 of the engine). It handles both pluralization (e.g., "leaf""leaves") and singularization (e.g., "leaves""leaf").

How it works

The engine uses a three-step technique that compiles to efficient BEAM bytecode:

  1. Extract the last byte<<_::binary-size(skip), last>> = word pulls the final byte in one instruction.

  2. Dispatch via select_val jump table — The extracted byte dispatches through function clauses (dispatch_plural(word, len, ?s), dispatch_plural(word, len, ?h), etc.). The BEAM compiler turns this into a select_val instruction — an O(1) jump table on the integer value, not sequential if/else checks.

  3. Confirm suffix via sized-skip match — Within each branch, a second binary match confirms the full suffix: <<prefix::binary-size(skip3), "sis">> = word. This is a single comparison, not a scan.

The result: O(1) dispatch to the correct suffix group, then O(1) suffix confirmation. No scanning, no regex compilation, no string reversal.

Dispatch branches

Pluralization

Last byteSuffixes handledExamples
?s-sis, -xis, -ous, -ois, -us, -is, -itis, -ssanalysis→analyses, cactus→cactuses, boss→bosses; classical: cactus→cacti, arthritis→arthritides
?e-ife, -mouseknife→knives, dormouse→dormice, otherwise append -s
?h-ch, -sh, -tooth, -fishchurch→churches, bucktooth→buckteeth, swordfish→swordfish
?yconsonant+y, vowel+ycategory→categories, day→days
?oall -o wordsphoto→photos (irregulars handle hero→heroes)
?xall -x wordsbox→boxes; classical: matrix→matrices, index→indices
?zall -z wordswaltz→waltzes
?f-f words (split by @f_takes_ves)leaf→leaves, roof→roofs, bluff→bluffs
?adefault -ssofa→sofas (Latin -um-a handled by irregulars)
?mdefault -sdrum→drums; classical: -um-a aquarium→aquaria
?n-person, -mansalesperson→salespeople, fireman→firemen
?d-childgrandchild→grandchildren, otherwise append -s
?t-footclubfoot→clubfeet, otherwise append -s
defaulteverything elsepost→posts

Singularization

Last byteSuffixes handledExamples
?s-ives, -ies, -ses, -zes, -xes, -ves, -oes, -men, -es, -ssknives→knife, categories→category, boxes→box
?a-ata-a (Greek), -a-um (Latin round-trip)traumata→trauma, aquaria→aquarium
?i-i-us (Latin round-trip)cacti→cactus, foci→focus
?e-mice, -people, -ae-adormice→dormouse, salespeople→salesperson, antennae→antenna
?h-teeth, -fishbuckteeth→bucktooth, swordfish→swordfish (unchanged)
?n-children, -mengrandchildren→grandchild, firemen→fireman
?t-feetclubfeet→clubfoot
defaultno change(non--s endings are not English plurals)

Performance

Benchmarked at 173K iterations/second on OTP 28 / Elixir 1.19 for a 35-word batch covering all suffix types — 48x faster than regex-based approaches (3.6K ips).

Usage

This module is not called directly. Plurality.Engine calls apply_plural_rule/1 and apply_singular_rule/1 as Tier 3 of the resolution pipeline. Both functions expect a downcased word that has already been checked against uncountables and irregulars.

Summary

Functions

Applies suffix rules to produce the plural form of a downcased word.

Applies suffix rules to produce the singular form of a downcased word.

Functions

apply_plural_rule(word, classical? \\ false)

@spec apply_plural_rule(word :: String.t(), boolean()) :: String.t()

Applies suffix rules to produce the plural form of a downcased word.

This function is Tier 3 of the resolution engine. It should only be called after the word has been checked against uncountables (Tier 1) and irregulars (Tier 2) by Plurality.Engine.

The word is assumed to be already downcased. The result is always lowercase; Plurality.Style.match_style/2 is applied by the engine afterward.

When classical? is true, Latin/Greek suffix rules are used: -us-i, -um-a, -ix/-ex-ices, -itis-itides.

Examples

iex> Plurality.Rules.apply_plural_rule("leaf")
"leaves"

iex> Plurality.Rules.apply_plural_rule("church")
"churches"

iex> Plurality.Rules.apply_plural_rule("category")
"categories"

iex> Plurality.Rules.apply_plural_rule("post")
"posts"

iex> Plurality.Rules.apply_plural_rule("")
""

iex> Plurality.Rules.apply_plural_rule("focus", true)
"foci"

iex> Plurality.Rules.apply_plural_rule("aquarium", true)
"aquaria"

apply_singular_rule(word)

@spec apply_singular_rule(word :: String.t()) :: String.t()

Applies suffix rules to produce the singular form of a downcased word.

This function is Tier 3 of the resolution engine. It should only be called after the word has been checked against irregular plurals (Tier 2) and uncountables (Tier 1) by Plurality.Engine.

The word is assumed to be already downcased. The result is always lowercase; Plurality.Style.match_style/2 is applied by the engine afterward.

Examples

iex> Plurality.Rules.apply_singular_rule("churches")
"church"

iex> Plurality.Rules.apply_singular_rule("categories")
"category"

iex> Plurality.Rules.apply_singular_rule("posts")
"post"

iex> Plurality.Rules.apply_singular_rule("analyses")
"analysis"

iex> Plurality.Rules.apply_singular_rule("")
""