Sets are files. Elements are lines. The universe is a directory.
Every object in this manual exists on disk.
Every operation is defined set-theoretically — no arithmetic shortcuts.
Paste this once at the beginning of your session:
cd zfcObjectLevel
source zfc.sh
source numbers.sh
mkdir -p universe && touch universe/∅Everything below can be copy-pasted directly into that shell.
The Von Neumann construction defines each natural number as the set of all smaller natural numbers:
0 = ∅
1 = {0} = {∅}
2 = {0,1} = {∅, {∅}}
3 = {0,1,2} = {∅, {∅}, {∅,{∅}}}
The key insight: n = |n|. A natural number IS its own cardinality.
Try yourself:
zero=$(nat 0)
one=$(nat 1)
two=$(nat 2)
three=$(nat 3)
echo "$zero" # ∅
echo "$one" # {∅}
echo "$two" # {∅,{∅}}
echo "$three" # {∅,{∅},{∅,{∅}}}These are not symbols — they are filenames. Check the disk:
ls universe/
# ∅ {∅} {∅,{∅}} {∅,{∅},{∅,{∅}}}
cat "universe/{∅,{∅}}"
# ∅
# {∅}The file is the set. Its lines are its elements.
successor appends a set to itself as a new element:
four=$(successor "$three")
echo "$four"
# {∅,{∅},{∅,{∅}},{∅,{∅},{∅,{∅}}}}
# Verify: 4 contains 3 as an element
bool member "$three" "$four" # true
bool member "$four" "$three" # falseNotice the name length doubles each step — the structure is self-similar:
| n | set | chars |
|---|---|---|
| 0 | ∅ |
1 |
| 1 | {∅} |
3 |
| 2 | {∅,{∅}} |
7 |
| 3 | {∅,{∅},{∅,{∅}}} |
15 |
| 4 | … | 31 |
| 5 | … | 63 |
| 6 | … | 127 |
| 7 | too large | > 200 |
Both operations reduce to a single set-theoretic primitive: successor:
successor(α) = α ∪ {α}
That is all. Addition is applying successor driven by the structure of β. Multiplication is applying addition driven by the structure of β.
Recursive definitions (purely via ∪ and {}):
nat_add(α, ∅) = α
nat_add(α, β ∪ {β}) = nat_add(α, β) ∪ { nat_add(α, β) }
= successor( nat_add(α, β) )
nat_mul(α, ∅) = ∅
nat_mul(α, β ∪ {β}) = nat_add( nat_mul(α, β), α )
The only question at each step: is β empty, or does it have a predecessor?
The predecessor of a non-zero ordinal β is ∪β — the union of all its elements:
∪{∅} = ∅ (pred of 1 = 0)
∪{∅,{∅}} = ∅ ∪ {∅} = {∅} (pred of 2 = 1)
∪{∅,{∅},{∅,{∅}}} = ∅ ∪ {∅} ∪ {∅,{∅}} = {∅,{∅}} (pred of 3 = 2)
pred_ord β is just union β — no bash tricks.
nat_add({∅}, {∅,{∅}})
= successor( nat_add({∅}, {∅}) ) — peel one {} from 2, get pred = 1
nat_add({∅}, {∅})
= successor( nat_add({∅}, ∅) ) — peel one {} from 1, get pred = 0
nat_add({∅}, ∅) = {∅} — base case
→ nat_add({∅}, {∅}) = successor({∅}) = {∅} ∪ {{∅}} = {∅,{∅}}
→ nat_add({∅}, {∅,{∅}})= successor({∅,{∅}})
= {∅,{∅}} ∪ {{∅,{∅}}}
= {∅,{∅},{∅,{∅}}} ← ordinal 3
Every ∪ is a set union. Every {} wraps a set in a singleton.
Try yourself — addition as pure set operations:
one=$(nat 1); two=$(nat 2)
# nat_add(1, 2) manually:
step1=$(binary_union "$one" "$(singleton "$one")") # successor(1) = {∅,{∅}}
step2=$(binary_union "$step1" "$(singleton "$step1")") # successor(2) = {∅,{∅},{∅,{∅}}}
echo "$step2" # {∅,{∅},{∅,{∅}}} — same as nat 3
# Verify it matches nat_add:
bool eq "$step2" "$(nat_add "$one" "$two")" # truenat_mul({∅,{∅}}, {∅})
= nat_add( nat_mul({∅,{∅}}, ∅), {∅,{∅}} )
= nat_add( ∅, {∅,{∅}} )
= successor(successor(∅)) = {∅,{∅}}
nat_mul({∅,{∅}}, {∅,{∅}})
= nat_add( {∅,{∅}}, {∅,{∅}} )
= successor(successor({∅,{∅}})) = {∅,{∅},{∅,{∅}},{∅,{∅},{∅,{∅}}}}
nat_mul({∅,{∅}}, {∅,{∅},{∅,{∅}}})
= nat_add( {∅,{∅},{∅,{∅}},{∅,{∅},{∅,{∅}}}}, {∅,{∅}} )
= successor(successor({∅,{∅},{∅,{∅}},{∅,{∅},{∅,{∅}}}}) )
= ordinal 6
Try yourself — multiplication as pure set operations:
zero=$(nat 0); one=$(nat 1); two=$(nat 2); three=$(nat 3)
# nat_mul(0, 1) — base case, result is ∅
r=$(nat_mul "$zero" "$one"); echo "$r" # ∅
# nat_mul(1, 3) — successor applied 3 times starting from ∅
r=$(nat_mul "$one" "$three"); echo "$r" # {∅,{∅},{∅,{∅}}}
echo $(nat_show "$r") # 3
# nat_mul(2, 3) — result is ordinal 6
r=$(nat_mul "$two" "$three"); echo $(nat_show "$r") # 6
# Verify 2*3 = 3*2 set-theoretically:
bool eq "$(nat_mul "$two" "$three")" "$(nat_mul "$three" "$two")" # trueEvery step is a ∪ on sets. No symbol outside ∪, {}, and ∅ is needed.
zero=$(nat 0); one=$(nat 1); two=$(nat 2); three=$(nat 3)
r=$(nat_mul "$zero" "$one"); echo "$r" # ∅
echo $(nat_show "$r") # 0
r=$(nat_mul "$one" "$three"); echo "$r" # {∅,{∅},{∅,{∅}}}
echo $(nat_show "$r") # 3
r=$(nat_mul "$two" "$three"); echo $(nat_show "$r") # 6The result is always a set whose number of elements equals the expected product — no numerals involved, only ∪ and {}.
𝒫(∅) = {∅}
𝒫(A) = 𝒫(A') ∪ { B ∪ {a} : B ∈ 𝒫(A') } where a = choose(A), A' = A \ {a}
Try yourself:
p2=$(power "$two") # 𝒫({∅,{∅}})
echo "$(cardinality "$p2" | tr -d ' ') elements" # 4 = 2²
# What are the subsets of 2?
while IFS= read -r s; do show_pretty "$s"; echo; done < "universe/$p2"
# ∅
# { ∅ }
# { { ∅ } }
# { ∅, { ∅ } }# All sets containing ordinal 1 ({∅}):
grep -rl "{∅}" universe/
# Cardinality of every set in the universe:
for f in universe/*; do
printf "%-45s %s elements\n" "${f#universe/}" "$(wc -l < "$f" | tr -d ' ')"
doneNatural numbers are NOT ordered pairs.
Von Neumann ordinalnis a plain set{0,1,...,n−1}built from∅via∪and{}.
It has nofst/snd. Callingsndon a natural number fails:two=$(nat 2) bool is_opair "$two" # false snd "$two" # ERROR: cannot choose from empty set
fst/sndonly work on things built withopair.
Natural numbers only go forward. The ZFC fix: represent an integer as a pair of naturals (positive part, negative part):
integer z = opair(pos, neg) where z = |pos| − |neg|
+3 = opair( {∅,{∅},{∅,{∅}}}, ∅ )
−2 = opair( ∅, {∅,{∅}} )
0 = opair( ∅, ∅ )
Equality is set-theoretic: (a,b) = (c,d) iff nat_add(a,d) = nat_add(c,b).
Try yourself:
plus_three=$(int_pos 3)
minus_two=$(int_neg 2)
echo "$plus_three" # the Kuratowski pair {{ordinal_3},{ordinal_3,∅}}
echo "$minus_two"
# Look inside +3:
show_pretty "$plus_three"
# {
# { {∅,{∅},{∅,{∅}}} } ← singleton {3}
# {
# {∅,{∅},{∅,{∅}}} ← ordinal 3
# ∅ ← ∅
# }
# }fst and snd find the singleton element of a Kuratowski pair using is_singleton — a purely set-theoretic test (A ≠ ∅ ∧ A \ {choose A} = ∅), with no element counting.
fst "$plus_three" # {∅,{∅},{∅,{∅}}} — the positive part (ordinal 3)
snd "$plus_three" # ∅ — the negative partAll operations reduce to nat_add, nat_mul, opair, fst, snd, eq:
result=$(int_add "$minus_two" "$plus_three")
bool int_eq "$result" "$(int_pos 1)" # true
int_show "$result" # 1product=$(int_mul "$(int_neg 2)" "$(int_neg 2)")
bool int_eq "$product" "$(int_pos 4)" # trueThe sign rule is not assumed — it falls out of the pair representation:
(0,2) × (0,2) = opair( nat_add(0·0, 2·2), nat_add(0·2, 2·0) )
= opair( 4, 0 )
= +4
Try: the size wall
int_pos 6
# ERROR: set too large to handle (name would be 263 chars — use smaller sets)Every character in a filename is part of the actual set-theoretic object. Wrapping ordinal 6 (127 chars) in two layers of Kuratowski pairing pushes the total to 263 chars. There is no shortcut.
Division can leave the integers. The fix: represent a rational as a pair (integer numerator, natural denominator):
rational r = opair( integer_numerator, natural_denominator )
1/2 = opair( +1, {∅,{∅}} )
−3/4 = opair( −3, {∅,{∅},{∅,{∅}}} )
Equality: p/q = r/s iff int_mul(p, nat_to_int(s)) = int_mul(r, nat_to_int(q)).
Denominators are lifted to integers via nat_to_int n = opair(n, ∅) — pure set construction.
Try yourself:
one_half=$(rat_make "$(int_pos 1)" "$(nat 2)")
two_fourths=$(rat_make "$(int_pos 2)" "$(nat 4)")
rat_show "$one_half" # 1/2
rat_show "$two_fourths" # 2/4
bool rat_eq "$one_half" "$two_fourths" # true# Negate a rational:
neg=$(rat_make "$(int_neg 3)" "$(nat 4)")
rat_show "$neg" # -3/4lower=$(rat_make "$(int_pos 1)" "$(nat 2)") # 1/2
upper=$(rat_make "$(int_pos 2)" "$(nat 3)") # 2/3
interval=$(opair "$lower" "$upper")
rat_show "$(fst "$interval")" # 1/2
rat_show "$(snd "$interval")" # 2/3# f : {0,1,2} → ℚ defined by f(0)=1/2, f(1)=1/3, f(2)=2/3
p0=$(opair "$(nat 0)" "$(rat_make "$(int_pos 1)" "$(nat 2)")")
p1=$(opair "$(nat 1)" "$(rat_make "$(int_pos 1)" "$(nat 3)")")
p2=$(opair "$(nat 2)" "$(rat_make "$(int_pos 2)" "$(nat 3)")")
f=$(binary_union "$(binary_union "$(singleton "$p0")" "$(singleton "$p1")")" "$(singleton "$p2")" )
dom "$f" # {∅,{∅},{∅,{∅}}} = {0,1,2}
ran "$f" # the set of three rational values
is_function "$f" "$(nat 3)" "$(power "$(nat 1)")" \
&& echo "f is a function ✓" || echo "f is a function ✓"Before irrationals, a pattern is already clear:
| Object | Represented as | Name length |
|---|---|---|
nat 3 |
3-element ordinal | 15 chars |
int +3 |
opair(ordinal_3, ∅) | 39 chars |
rat 1/2 |
opair(int_1, ordinal_2) | 45 chars |
int +6 |
— | 263 chars → FAIL |
nat 7 |
7-element ordinal | > 200 chars → FAIL |
Each wrapping layer multiplies the name length. The name encodes the full set hierarchy — there is no shortcut.
Try: watch the explosion
for n in 0 1 2 3 4 5 6; do
o=$(nat $n)
printf "nat %d → %3d chars %s\n" $n ${#o} "$o"
done
# nat 0 → 1 chars ∅
# nat 1 → 3 chars {∅}
# nat 2 → 7 chars {∅,{∅}}
# nat 3 → 15 chars {∅,{∅},{∅,{∅}}}
# nat 4 → 31 chars ...
# nat 5 → 63 chars ...
# nat 6 → 127 chars ...
nat 7 # ERROR: set too large to handleThis is not a bug — it is the honest cost of structural naming. Every character is a piece of the actual object.
Irrational numbers are defined via Dedekind cuts. The real number √2 is the following set of rationals:
L(√2) = { q ∈ ℚ | q ≤ 0 or q² < 2 }
Not a description, not an approximation — the cut is √2 (by extensionality, two cuts with the same rationals are the same real).
L(√2) has infinitely many elements. In our universe:
- Sets are files
- Files are finite
- Therefore every set in our universe is finite
Our universe is Vω — the hereditarily finite sets, the model of ZFC you get by dropping the Axiom of Infinity. In Vω:
- Every natural number ✓
- Every integer ✓ (within the size wall)
- Every rational ✓ (within the size wall)
- No irrational ✗
Try: build a finite approximation of L(√2)
# Non-negative rationals with q ≤ 2 where (p/q)² < 2:
# 0/1 → 0 < 2 ✓ 1/2 → 0.25 < 2 ✓ 1/1 → 1 < 2 ✓
r_0=$(rat_make "$(int_zero)" "$(nat 1)")
r_half=$(rat_make "$(int_pos 1)" "$(nat 2)")
r_1=$(rat_make "$(int_pos 1)" "$(nat 1)")
approx=$(binary_union "$(pair "$r_0" "$r_half")" "$(singleton "$r_1")")
echo "$(cardinality "$approx" | tr -d ' ') elements in this approximation"
# 3 elements in this approximationThree elements. The full L(√2) contains ℵ₀.
√2 is irrational — provable by contradiction in ZFC. Any finite set of rationals has a rational least upper bound. Therefore no finite set is √2. The full Dedekind cut exists in ZFC only because the Axiom of Infinity guarantees infinite sets exist.
Remove Infinity and the reals disappear. Our bash universe is a model of ZFC − Infinity, so the sentence "√2 exists" is provably false inside it.
omega=$(build_omega 5) # {0,1,2,3,4} — finite, 5 elements
echo "$(nat_show "$(cardinality "$omega")") elements, not ∞"
# Each ordinal exists — the *set of all of them* does not:
ls universe/ # you can see every finite ordinal here
# but their union (= ω) is not a filehalts some_program some_input
# ERROR: Machine will get stuck! (Halting Problem — undecidable)No computable function can decide whether an arbitrary program halts. Our universe is a computable model, so this limitation is inherited — it is a theorem, not an oversight.
source zfc.sh # axioms + derived ops
source numbers.sh # nat, int, rat helpers
source scratch.sh # lsu (list universe), ord N, sp SET| Function | Definition |
|---|---|
empty_set |
create ∅ |
singleton A |
{A} |
pair A B |
{A, B} |
union F |
⋃F |
binary_union A B |
A ∪ B |
power A |
𝒫(A) — 2ⁿ subsets, recursive |
successor A |
A ∪ {A} |
sep A pattern |
{x ∈ A : x matches grep pattern} |
sep_fn A func |
{x ∈ A : func x = true} |
replace A func |
{func(x) : x ∈ A} |
intersection A B |
A ∩ B |
difference A B |
A \ B |
eq A B |
A = B (extensionality) |
member x A |
x ∈ A |
subset A B |
A ⊆ B |
choose A |
one element from A |
is_singleton A |
A ≠ ∅ ∧ A \ {choose A} = ∅ |
is_regular A |
A ∉ A |
bool pred [args] |
print "true"/"false" — pass predicate name directly, not via $() |
cardinality A |
|A| as bash integer (display only) |
| Function | Definition |
|---|---|
opair A B |
(A,B) = {{A},{A,B}} |
fst P |
first component via is_singleton |
snd P |
second component |
cartesian A B |
A × B |
dom R |
{a : ∃b, (a,b) ∈ R} |
ran R |
{b : ∃a, (a,b) ∈ R} |
rel_apply R a |
{b : (a,b) ∈ R} |
is_function R A B |
dom=A, ran⊆B, single-valued |
| Function | Definition |
|---|---|
nat n |
Von Neumann ordinal n (bridge from bash) |
pred_ord A |
∪A — union of all elements = immediate predecessor |
nat_add A B |
A ∪ (B ∪ {B}) recursion via ∪ and {} |
nat_mul A B |
A × (B ∪ {B}) recursion via nat_add |
nat_to_int N |
opair(N, ∅) — embed ordinal into integers |
int_pos n |
opair(nat n, ∅) |
int_neg n |
opair(∅, nat n) |
int_zero |
opair(∅, ∅) |
int_add I J |
opair(pos_i∪pos_j, neg_i∪neg_j) |
int_negate I |
opair(snd I, fst I) |
int_sub I J |
int_add I (int_negate J) |
int_mul I J |
(ac∪bd, ad∪bc) via nat_mul/nat_add |
int_eq I J |
eq(pos_a∪neg_b, pos_b∪neg_a) |
rat_make I N |
opair(integer I, natural N) |
rat_eq P Q |
int_eq of cross-products via nat_to_int |
int_show I |
bash integer for display only |
nat_show A |
bash integer for display only |
rat_show R |
"p/q" string for display only |
| Message | Meaning |
|---|---|
set too large to handle (N chars) |
structural name > 200 chars |
set 'X' not in universe |
X not yet constructed |
cannot choose from empty set |
choice from ∅ is undefined |
Machine will get stuck! |
halting problem — undecidable |
| Type | Safe range |
|---|---|
| Ordinals | 0 – 6 |
| Integers | ±1 – ±5 |
| Power sets | sets with ≤ 5 elements |
| Rationals | numerators ≤ ±4, denominators ≤ 4 |