Programming with Nothing
Programming with Nothing: FizzBuzz built from lambdas and nothing else - no Integer, no Boolean, no if, no % - just Church encodings, assembled layer by layer like civilization: numerals first, then arithmetic, then predicates and recursion (the Z combinator, because Y diverges under strict evaluation), then FizzBuzz itself. Each layer is a plan task whose referee converts back to native Ruby ONLY at the boundary to check the layer's laws, and the final output must equal …
Testing & Verification
Round 20
Tom Stuart
exit 0
bundle exec ruby examples/programming_with_nothing.rb
a real captured run
PROGRAMMING WITH NOTHING (lambdas all the way down; turtles found unnecessary)
layer numerals 3/3 laws hold
layer arithmetic 4/4 laws hold
layer predicates + Z 6/6 laws hold
layer fizzbuzz 15/15 laws hold
and the payoff, computed without a single Integer in the logic:
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
what the stunt is FOR: every layer of convenience your language
hands you - numbers, booleans, if, %, recursion - is a library
that somebody could have written in the layer below, and here
somebody did, in 25 lines. the Z combinator earns special
mention: Y diverges under Ruby's strict evaluation, so recursion
itself needed an eta-expansion to survive - evaluation ORDER is
a real dependency, usually invisible until you build without
the safety net. the plan assembled civilization in dependency
order with a referee per layer, which is also how you'd want
any bootstrap to go: certify arithmetic before you trust the
things built on it. happy Why Day; the chunky bacon is implied.
source
# frozen_string_literal: true # Programming with Nothing: FizzBuzz built from lambdas and nothing # else - no Integer, no Boolean, no if, no % - just Church # encodings, assembled layer by layer like civilization: numerals # first, then arithmetic, then predicates and recursion (the Z # combinator, because Y diverges under strict evaluation), then # FizzBuzz itself. Each layer is a plan task whose referee converts # back to native Ruby ONLY at the boundary to check the layer's # laws, and the final output must equal native FizzBuzz exactly. # Why do this? Because it's Why Day somewhere, and because nothing # teaches what a language gives you like building a language out # of its smallest part. Lambdas all the way down; turtles found # unnecessary. # # bundle exec ruby examples/programming_with_nothing.rb # # Runs offline; exits 1 unless every layer's laws hold and the # lambda FizzBuzz matches the native one on 1..15. require class="s">"bundler/setup" require class="s">"agentic" Agentic.logger.level = class="y">:fatal # --- layer 0: numbers, from nothing -------------------------------------------------- ZERO = ->(f) { ->(x) { x } } SUCC = ->(n) { ->(f) { ->(x) { f[n[f][x]] } } } NUMS = (0..15).reduce([ZERO]) { |acc, _| acc + [SUCC[acc.last]] } # NUMS[i] is Church i # --- layer 1: arithmetic -------------------------------------------------------------- ADD = ->(m) { ->(n) { ->(f) { ->(x) { m[f][n[f][x]] } } } } MULT = ->(m) { ->(n) { ->(f) { m[n[f]] } } } PRED = ->(n) { ->(f) { ->(x) { n[->(g) { ->(h) { h[g[f]] } }][->(_) { x }][->(u) { u }] } } } SUBTRACT = ->(m) { ->(n) { n[PRED][m] } } # --- layer 2: truth, comparison, recursion -------------------------------------------- TRUE_ = ->(a) { ->(b) { a } } FALSE_ = ->(a) { ->(b) { b } } IF_ = ->(c) { ->(t) { ->(f) { c[t][f] } } } IS_ZERO = ->(n) { n[->(_) { FALSE_ }][TRUE_] } LEQ = ->(m) { ->(n) { IS_ZERO[SUBTRACT[m][n]] } } # The linter flags x[x] as suspicious. It is. Self-application is the # whole trick of a fixed-point combinator; Russell objected too. Z = ->(f) { ->(x) { f[->(v) { x[x][v] }] }[->(x) { f[->(v) { x[x][v] }] }] } # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands MOD = Z[->(f) { ->(m) { ->(n) { IF_[LEQ[n][m]][->(x) { f[SUBTRACT[m][n]][n][x] }][m] } } }] # --- the boundary: the only place native Ruby is allowed to peek ---------------------- def to_integer(n) = n[->(x) { x + 1 }][0] def to_boolean(b) = b[true][false] # --- layer 3: fizzbuzz, with native strings admitted at the very edge ----------------- LAMBDA_FIZZBUZZ = ->(n) { IF_[IS_ZERO[MOD[n][NUMS[15]]]][class="s">"FizzBuzz"][ IF_[IS_ZERO[MOD[n][NUMS[3]]]][class="s">"Fizz"][ IF_[IS_ZERO[MOD[n][NUMS[5]]]][class="s">"Buzz"][to_integer(n).to_s]]] } NATIVE_FIZZBUZZ = ->(i) { if i % 15 == 0 class="s">"FizzBuzz" else (if i % 3 == 0 class="s">"Fizz" else ((i % 5 == 0) ? class="s">"Buzz" : i.to_s) end) end } # --- civilization, assembled as a plan: each layer certifies its laws ----------------- LAYERS = [ {name: class="s">"numerals", laws: -> { [to_integer(NUMS[0]) == 0, to_integer(NUMS[7]) == 7, to_integer(SUCC[NUMS[14]]) == 15] }}, {name: class="s">"arithmetic", laws: -> { [to_integer(ADD[NUMS[3]][NUMS[4]]) == 7, to_integer(MULT[NUMS[3]][NUMS[5]]) == 15, to_integer(PRED[NUMS[9]]) == 8, to_integer(SUBTRACT[NUMS[9]][NUMS[4]]) == 5] }}, {name: class="s">"predicates + Z", laws: -> { [to_boolean(IS_ZERO[ZERO]), !to_boolean(IS_ZERO[NUMS[3]]), to_boolean(LEQ[NUMS[3]][NUMS[9]]), !to_boolean(LEQ[NUMS[9]][NUMS[3]]), to_integer(MOD[NUMS[14]][NUMS[5]]) == 4, to_integer(MOD[NUMS[15]][NUMS[3]]) == 0] }}, {name: class="s">"fizzbuzz", laws: -> { (1..15).map { |i| LAMBDA_FIZZBUZZ[NUMS[i]] == NATIVE_FIZZBUZZ[i] } }} ].freeze orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 1) # civilization is sequential previous = nil certified = {} LAYERS.each do |layer| task = Agentic:class="y">:Task.new(description: layer[class="y">:name], agent_spec: {class="s">"name" => layer[class="y">:name], class="s">"instructions" => class="s">"prove"}) orchestrator.add_task(task, previous ? [previous] : [], agent: ->(_t) { certified[layer[class="y">:name]] = layer[class="y">:laws].call }) previous = task end orchestrator.execute_plan puts class="s">"PROGRAMMING WITH NOTHING (lambdas all the way down; turtles found unnecessary)" puts certified.each { |name, laws| puts format(class="s">" layer %-14s %d/%d laws hold", name, laws.count(&class="y">:itself), laws.size) } puts puts class="s">" and the payoff, computed without a single Integer in the logic:" row = (1..15).map { |i| LAMBDA_FIZZBUZZ[NUMS[i]] } puts class="s">" #{row.join(" class="s">")}" puts failures = certified.reject { |_, laws| laws.all? }.keys puts class="s">" what the stunt is FOR: every layer of convenience your language" puts class="s">" hands you - numbers, booleans, if, %, recursion - is a library" puts class="s">" that somebody could have written in the layer below, and here" puts class="s">" somebody did, in 25 lines. the Z combinator earns special" puts class="s">" mention: Y diverges under Ruby's strict evaluation, so recursion" puts class="s">" itself needed an eta-expansion to survive - evaluation ORDER is" puts class="s">" a real dependency, usually invisible until you build without" puts class="s">" the safety net. the plan assembled civilization in dependency" puts class="s">" order with a referee per layer, which is also how you'd want" puts class="s">" any bootstrap to go: certify arithmetic before you trust the" puts class="s">" things built on it. happy Why Day; the chunky bacon is implied." exit(failures.empty? ? 0 : 1)