Repository navigation
Archive Survey Haskell
Carried over from the earlier enumeratio. Written against the old Postgres
base_trait/base_categoryregistries, which are gone, but the capability ladder it argues for (Universe→Finite→ rankable → graded → sampleable) is exactly the shape@enumeratio/structuresand the Plausible capability model now build in compute-engine terms — see Structures and Speculative Plausible. Read the steal/avoid verdicts against those, not against the retired registries.
One of the ecosystem surveys (
../spikes.md→ Ecosystem surveys). Feedsbase_trait/base_categoryand the enumeration-capability traits (../backlog.md#8). Cross-checks Haskell against the (still unwritten) Sage / Rust / Lean surveys for a convergent capability vocabulary — the Sage/Rust/Lean columns here are drawn from those systems directly (the brief's "your knowledge"), to be reconciled when those docs land.
-
Steal: the capability ladder —
Universe(lazily enumerable) →Finite(has cardinality) → rankable (unrank) → graded (size-stratified) → sampleable (uniform random). It's the shape backlog #8 is reaching for, and it lands cleanly asbase_traitrows closed overimplies. Stealfeat'sFinite { fCard, fIndex }as the canonical (cardinality, unrank) bundle, size-stratified enumeration (feat[Finite]/ LeanChecktiers :: a) as the model forgraded, andfeat'suniform(sample a bounded prefix) as the answer to #8's infinite/unknown-count case. -
Avoid: the
Nummonolith (a god-class you can't join without paying for everything) and baking cost into a capability's identity. Don't chase higher-kinded /Functor-style machinery — it's elegant and it doesn't map to a concrete-carrier data catalog. -
Validated: enumeratio's capability-as-data (
base_traitrows +impliesclosure) is exactly what Haskell had to bolt on withConstraintKinds/Dict. We get for free — and can re-slice as a view — what Haskell bakes intobaseat compile time and pays for at every migration (AMP/FTP).
Haskell's core separation is typeclass = capability, instance = a representation claiming it:
class Eq a where (==) :: a -> a -> Bool -- capability: "can be compared"
instance Eq Bool where ... -- Bool is one representation that has itThe class is an interface over an abstract type; an instance binds it to a concrete type. A function constrains what it
needs (sort :: Ord a => [a] -> [a]) and stays blind to representation. This is the same split enumeratio draws between a
capability a collection HAS (base_trait) and the carrier/collection that realizes it.
Entailment is a DAG. Superclass constraints declare that one capability presupposes another:
class Eq a => Ord a -- can-order ⇒ can-equate
class Num a => Fractional a -- has-division ⇒ is-a-Num
class Universe a => Finite a -- is-finite ⇒ is-enumerableThis is base_trait.implies / Rust supertraits / Sage supercategories — the realizer comment already names the parallel.
One refinement enumeratio has over Haskell: Haskell has a single edge type (superclass), and uses one class to mean both
"capability" (Foldable = can fold) and "algebraic structure with laws" (Monoid = is a monoid). enumeratio splits
that into three typed edges — base_trait.implies (capability ⇒ capability), base_category.parents (structure ⇒ super-
structure), base_category.requires (structure ⇒ its axiom-traits). Keep that finer typing; it's the thing Sage half-has
and Haskell lacks.
Constraints as first-class values. ConstraintKinds promotes a constraint to the Constraint kind, and the
constraints package reifies it as data: Dict :: p => Dict p captures a dictionary; (:-) :: Constraint -> Constraint -> Type is entailment as a value you can pass and compose. This is Haskell straining to make its class system into data.
enumeratio already lives on the other side of that line — a trait is a row, implies is a column, the closure is a
recursive view. The takeaway isn't a feature to build; it's a validation: keep deriving assignments from the registries and
closing over implies (as base_collection_trait does) rather than hand-wiring — the data form is the whole advantage.
Laziness → infinite enumeration. universe :: [a] and [1..] are lazy: an enumeration can be infinite and still a
value, consumed prefix-by-prefix. Crucially, finiteness is a separate capability you assert only when the list ends
(Finite subclass), and cardinality lives on Finite, not on Universe. You can enumerate without being able to count.
That is precisely enumeratio's unbounded flag → finite trait, and precisely the support-function taxonomy #8 wants: the
enumeration is always available; counting and random access are additional, declarable capabilities.
Num a is base's numeric root, and it's the community's standard example of a god-class: to be Num you must supply
(+), (*), abs, signum, fromInteger, negate all at once, with Eq/Show historically dragged in as
superclasses. There is no way to be additive-only, or a semiring, or a ring-without-signum. The hierarchy is "convenient but
not principled": no stated laws (associativity is assumed, never declared), superfluous superclasses, and too coarse to
express real algebra.
The fixes — numeric-prelude (Algebra.Additive → Ring → Field …), Kmett's algebra, numhask, the semirings
package — all do the same thing: shatter the monolith into fine-grained, composable classes with mathematical names, so
you claim exactly the structure you have. (PureScript rebased its Prelude on Semiring for this reason.)
Two lessons land on enumeratio:
-
Fine-grained beats monolithic (the algebra/operations lattice,
design/structure lattice magma→field, already leans this way). Don't grow a "fully enumerable" mega-trait; keep the rungs separate and letimpliescompose them. -
Retrofit is the real cost. The Applicative-Monad and Foldable-Traversable Proposals (AMP/FTP) took years because
Haskell's superclass DAG is compiled into
baseand every instance breaks when it moves. enumeratio'simpliesclosure is a view over rows — re-slicing the hierarchy is a query, not an ecosystem migration. That asymmetry is the strongest argument for keeping the capability graph data-driven (per the registry-is-data-driven principle), never hardcoded in engine code.
Haskell has several independent takes on "enumerate a type," and their union is a clean capability ladder.
Enum (base). toEnum :: Int -> a / fromEnum :: a -> Int / enumFrom :: a -> [a]. A weak, partial rank/unrank plus
lazy successor-enumeration — but lawless and Int-bounded, so not a serious model. Its lesson is only that succ/toEnum
(next/unrank) are recognized as distinct capabilities.
universe package — the countability split, stated as two classes:
class Universe a where universe :: [a] -- "recursively enumerable" (may be infinite)
class Universe a => Finite a where
universeF :: [a]
cardinality :: Tagged a Natural -- "the enumeration eventually ends"Universe = can list them lazily; Finite = and the list ends, with a known count. The superclass edge is the
implies. This is the cleanest statement of enumeratio's unbounded → finite distinction anywhere in the survey set.
feat (testing-feat) — the closest analog to enumeratio's engine. It represents an enumeration as a size-indexed
sequence of finite, indexable parts:
data Finite a = Finite { fCard :: Index, fIndex :: Index -> a } -- Index = Integer
data Enumerate a = Enumerate { ... :: [Finite a] } -- one Finite per SIZE (constructor count)Finite's two fields are enumeratio's (cardinality, unrank) pair — fCard is cardinality, fIndex :: Index -> a
is unrank. Enumerate as a list of Finite parts, one per size is exactly grading: partition the enumeration into
finite, independently-counted, independently-indexable strata. The algebra is closed under sum/product/bijection — i.e. you
build a composite carrier's enumeration from its factors' enumerations, and a bijection transports one enumeration onto
another (this is enumeratio's order-isomorphic sibling collections; see §6). Test.Feat.Access adds index (global
unrank across parts) and uniform :: Enumerate a -> Int -> Gen a — uniform random selection from a bounded prefix,
which is the exact answer to #8's "define the infinite / unknown-count behaviour (sample a bounded prefix)."
LeanCheck — same grading idea, minimal surface: tiers :: a, a (possibly infinite) list of finite sublists of
same-and-increasing size. tiers :: a is a graded enumeration. Confirms the shape is convergent, not a feat quirk.
QuickCheck Arbitrary / feat uniform — the sampleable rung: arbitrary :: Gen a. Note the split QuickCheck vs
SmallCheck/feat/LeanCheck makes explicit — random sampling and exhaustive enumeration are different capabilities over the
same set, and feat derives both from one Enumerate (uniform = draw an index in [0, card), unrank). That is
verbatim #8's plan: "draw a uniform rank in [0, cardinality) and unrank."
-
arithmoi— efficient number theory. The relevant design point is theUniqueFactorisationtypeclass (factorise,isPrime,factorBack): factorization modeled as a capability, andfactorBack(recompose from prime powers) is its inverse — a capability + its inverse map, i.e. a bijection between an integer and its prime-exponent vector. Direct grist for the number-theory-gradings carrier (naturals-as-prime-exponent-vector) and for modeling factorization as abase_mapwith an inverse. -
combinat— generate/count/manipulate partitions, compositions, permutations, Young tableaux, lattice paths, trees. The nearest ecosystem to enumeratio's actual objects. Not a capability-model reference (it's concrete generators, à la Sage's constructors), but a coverage checklist and a source of counting formulas (e.g. tableaux via the hook-length formula) if we port more collections.
-
Higher-kinded classes (
Functor/Applicative/Monad/Foldable/Traversable f) — capabilities of a type constructor parameterized over its element type. Beautiful, and irrelevant to a catalog of concrete-carrier collections: a collection isn't parametric over an element type the way[]is. The one honest touch-point isFoldable/Traversable≈ "iterate the elements," which we already have as the enumeration capability — no HKT needed. - Global instance coherence — Haskell allows one instance per (class, type), forbidding orphans. enumeratio deliberately wants the opposite: many structures on one carrier (the sibling-collection pattern, §6). Don't import the coherence constraint; do borrow the mechanism Haskell uses to work around it — the newtype (§6).
- Reifying constraints as a feature — §1: we already have it as rows; there's nothing to build.
Haskell's answer to "one carrier, several structures" is the newtype: Sum/Product/Min/Max are distinct Monoid
instances over the same underlying number; Down a reverses an Ord. Same bits, different declared structure, kept
distinct because coherence forbids two instances on the raw type.
This is enumeratio's one-collection-one-carrier, siblings-borrow-order-via-order-iso rule (see the
one-collection-one-carrier / one-identity-many-roles design notes) stated in a second vocabulary. It's worth citing in the
maps/bijections doc: an alternate order is a sibling collection (a newtype), not a config flag, for the same reason
Haskell uses a newtype — the structure travels with the (re)named type, and the bijection between them is feat's
enumeration-transport. Convergent design, independently arrived at.
Reconciling Haskell against Rust / Sage / Lean-mathlib (the latter three from direct knowledge, pending their surveys):
| concept | Haskell | Rust | Sage | Lean / mathlib4 | enumeratio |
|---|---|---|---|---|---|
| capability | typeclass | trait | category (methods live on it) | class (typeclass) | base_trait |
| representation | instance (a type) |
impl (a type) |
Parent / Element | instance / structure
|
carrier / collection |
| entailment | superclass =>
|
supertrait :
|
supercategory |
extends / superclass |
implies (+ parents) |
| axioms / laws | convention only | doc convention | declared axioms | proved (proof fields) |
base_category.requires + oracle/golden tests |
| lazily enumerable |
Universe ([a]) |
Iterator (next) |
EnumeratedSets |
Countable |
enumerable (elements SRF) |
| next and prev | — | DoubleEndedIterator |
next/previous
|
— |
bidirectional (next/prev) |
| finite + counted |
Finite (cardinality) |
ExactSizeIterator::len |
FiniteEnumeratedSets |
Fintype (card, elems) |
finite + cardinality
|
| rank / unrank |
feat fIndex :: Index→a
|
— |
unrank / rank
|
Encodable (encode/decode), Denumerable (ℕ≃α) |
unrank(ordered_<coll>), checkable rank
|
| graded (by size) |
feat [Finite]; LeanCheck tiers::a
|
— | graded | graded / GradedRing
|
base_grade / graded
|
| uniform random |
feat uniform; QuickCheck Arbitrary
|
rand distributions |
random_element |
— |
random_element (#8) |
| reified capability |
ConstraintKinds Dict/:-
|
dyn Trait objects |
a category is an object | typeclass is a term | rows (already reified) |
Two divergences worth remembering: laws get progressively harder guarantees left-to-right (Haskell comments them → Rust
documents → Sage declares → Lean proves); enumeratio sits between Sage and Lean — it declares axioms
(base_category.requires) and tests them (oracle/golden), which is stronger than Haskell and cheaper than proof. And
Lean's ladder is the sharpest: Countable (∃ injection to ℕ) → Encodable (explicit encode/decode = rank/unrank) →
Denumerable (ℕ ≃ α, a total unrank bijection) → Fintype (finite, with card). That four-rung split is the reference
target for #8's taxonomy; Haskell's Universe/Finite + feat's fIndex cover the same rungs with running code.
Each rung is a base_trait; implies points from richer to the capabilities it's built on. A collection carries the
highest rung its ranking supports, and the closure fills in the rest.
-
enumerable— a lazyelements()stream; no count, no O(1) access. (HaskellUniverse, RustIterator, SageEnumeratedSets, LeanCountable.) The floor; every collection has it. -
bidirectional—nextandprevon the ranking. (RustDoubleEndedIterator, Sagenext/previous.) -
counted—cardinalityis known (finite). (HaskellFinite, RustExactSizeIterator, SageFiniteEnumeratedSets, LeanFintype.) ⇒enumerable. (This is today'sfinitetrait;countednames the capability — "you may callcardinality" — vs the property "the set is finite," which is the same edge here.) -
rankable—unrank(rank) → element(andrankwhere order-isomorphically checkable). (featfIndex, LeanEncodable/Denumerable, Sageunrank/rank.) ⇒enumerable; ⇒countedfor a finite carrier. -
graded— the enumeration splits into finite, independently counted+indexed size-strata. (feat[Finite], LeanChecktiers.) ⇒countedper stratum. (Alreadybase_grade/graded.) -
sampleable— uniformrandom_element. (featuniform, QuickCheckArbitrary, Sagerandom_element.) ⇒rankable+counted(drawrank ∈ [0, cardinality), unrank); for an unbounded carrier, degrade to a bounded-prefix sample or refuse —feat'suniformis the bounded-prefix precedent.
Steal
-
Adopt the ladder in §7 as
base_traitrows (enumerable,bidirectional,counted,rankable,graded,sampleable) withimpliesedges, derived from each ranking's support functions the waybase_collection_traitalready derivesfinite/graded/has_stats. This is backlog #8's "declare each collection's enumeration capability," and it drops into the existing view machinery unchanged. -
Name the (cardinality, unrank) bundle after
feat'sFinite.counted+rankabletraveling together is theFinite { fCard, fIndex }record; treat them as the pair that a random-access ranking provides, and letsampleableimplyboth. -
uniform= bounded-prefix sampling for the infinite/unknown-count case in #8. Finite+known → uniform over[0, cardinality); unbounded → sample a bounded prefix (document the prefix as the support), matchingfeat. -
graded= size-stratified[Finite]. Reconfirmsbase_grade: a grade axis partitions the enumeration into finite strata, each independently counted and unrankable — thefeat/LeanCheck shape. No change, but the framing sharpens the "grading is a partition of the enumeration" story. -
arithmoi'sfactorise/factorBackas the template for factorization-as-capability-plus-inverse-map — feeds the number-theory-gradings carrier and modeling factorization as abase_mapwith an inverse. - Cite the newtype ↔ sibling-collection parallel (§6) in the maps/bijections doc — it's independent corroboration that alternate structures on one carrier should be named siblings, not flags.
Avoid
-
No god-trait. The
Numlesson: don't fuse the ladder into one "enumerable" mega-capability. Keep the rungs separate and compose viaimplies(already the house style — this just says stay the course). -
Keep cost out of the capability's identity. #8 wants an asymptotic cost / size heuristic; Haskell (and Sage) encode
what capability exists, never how cheap. The closest anyone comes is Rust's
ExactSizeIterator(its existence asserts O(1)len). Recommendation: abase_traitsays the capability exists; a separate, orthogonal cost/tier annotation (the existing aggregate/filterable/groupable tiering) says how cheap. Don't mintrankable_o1vsrankable_scantraits — annotate cost on the assignment, keep the capability id clean. -
Skip HKT and instance-coherence. §5 — no
Functor-style machinery, and don't import the one-instance-per-type rule that the sibling-collection pattern is specifically designed to sidestep.
Validated (no action, worth stating)
- Capability-as-data (
base_traitrows +impliesview) is what Haskell reaches forConstraintKinds/Dictto fake, and what makes AMP/FTP-style re-slicing a query here instead of a multi-year ecosystem migration. The data-driven registry isn't just convenient — it's the structural advantage over a compiled class hierarchy. - The trait (HAS) / category (IS, with
requiresaxioms) split is finer than Haskell's single overloaded "class," and closer to the Sage picture. Keep the three typed edges.
-
testing-feat (Test.Feat, Test.Feat.Enumerate, Test.Feat.Access, Test.Feat.Class) —
Finite {fCard, fIndex},Enumerate = [Finite],uniform. - Feat: Functional Enumeration of Algebraic Types (Duregård, Jansson, Wang — Haskell '12)
-
universe / universe-base (Data.Universe.Class) —
Universe,Finite,cardinality. - leancheck (Test.LeanCheck) —
Listable/tiers :: a - arithmoi (Math.NumberTheory.Primes) —
UniqueFactorisation - combinat (Math.Combinat)
- numeric-prelude — fine-grained numeric hierarchy and Numeric Prelude (HaskellWiki)
-
constraints (Data.Constraint) —
Dict,:-and GHC — theConstraintkind