Every arithmetic operation here is a primitive recursive function — each one defined by running a bounded loop that calls the one below it.
<p class="hint">Each operation is built by stacking one more bounded for-loop on top of the previous one. Choose two numbers and an operation, then click <b>Compute</b> to see how many loop steps run.</p>
<div class="controls">
<label>a = <input id="inputA" type="number" value="3" min="0" max="9" /></label>
<label>b = <input id="inputB" type="number" value="4" min="0" max="9" /></label>
<div class="ops">
<button class="op-btn active" data-op="add">a + b</button>
<button class="op-btn" data-op="mul">a × b</button>
<button class="op-btn" data-op="exp">a ^ b</button>
</div>
<button id="compute">Compute</button>
</div>
<div id="result-box" class="result-box hidden">
<div class="result-label" id="result-label"></div>
<div class="result-val" id="result-val"></div>
<div class="step-trace" id="step-trace"></div>
<div class="loop-explain" id="loop-explain"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-direction: column; gap: .6rem; }
label { font-size: .95rem; display: flex; align-items: center; gap: .4rem; }
input[type=number] { width: 56px; padding: .25rem .4rem; font-size: 1rem;
border: 1px solid #ccc; border-radius: 6px; text-align: center; }
.ops { display: flex; gap: .4rem; flex-wrap: wrap; }
.op-btn { font: 600 14px system-ui; padding: .38rem .75rem; border: 2px solid #1d3557;
background: #fff; color: #1d3557; border-radius: 8px; cursor: pointer; transition: .15s; }
.op-btn.active { background: #1d3557; color: #fff; }
#compute { font: 600 14px system-ui; padding: .45rem 1rem; background: #e63946;
color: #fff; border: none; border-radius: 8px; cursor: pointer; align-self: flex-start; }
.result-box { margin-top: .9rem; padding: .8rem 1rem; border-radius: 10px;
background: #eef4fb; border: 1px solid #c8daea; }
.result-box.hidden { display: none; }
.result-label { font-size: .8rem; color: #556; text-transform: uppercase;
letter-spacing: .05em; margin-bottom: .2rem; }
.result-val { font-size: 2rem; font-weight: 700; color: #1d3557; line-height: 1.2; }
.step-trace { margin-top: .6rem; font-size: .85rem; color: #334; line-height: 1.6; }
.step { display: inline-block; background: #d0e4f7; border-radius: 4px;
padding: 0 .4em; margin: .1em; font-family: ui-monospace, monospace; }
.step.final { background: #1d3557; color: #fff; }
.loop-explain { margin-top: .5rem; font-size: .82rem; color: #556;
border-top: 1px solid #c8daea; padding-top: .5rem; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Primitive recursive arithmetic — every operation is a bounded for-loop stacked on the one below it.
//
// zero() = 0
// succ(n) = n + 1
//
// add(a, b) — b steps of succ starting from a:
// add(a, 0) = a
// add(a, s+1) = succ(add(a, s))
//
// mul(a, b) — b steps of "add a" starting from 0:
// mul(a, 0) = 0
// mul(a, s+1) = add(mul(a, s), a)
//
// exp(a, b) — b steps of "mul by a" starting from 1:
// exp(a, 0) = 1
// exp(a, s+1) = mul(exp(a, s), a)
function prAdd(a, b) {
const trace = [a];
let acc = a;
for (let step = 0; step < b; step++) {
acc = acc + 1; // succ
trace.push(acc);
}
return { result: acc, trace, steps: b, formula: `${a} + ${b}` };
}
function prMul(a, b) {
const trace = [0];
let acc = 0;
let totalSteps = 0;
for (let step = 0; step < b; step++) {
acc = acc + a; // add(acc, a) — itself b sub-steps but we count outer loops here
totalSteps += a; // inner add calls a steps
trace.push(acc);
}
return { result: acc, trace, steps: b, innerSteps: totalSteps, formula: `${a} × ${b}` };
}
function prExp(a, b) {
const trace = [1];
let acc = 1;
let totalMulSteps = 0;
for (let step = 0; step < b; step++) {
totalMulSteps += step === 0 ? a : acc; // mul(acc, a) loops
acc = acc * a; // mul(acc, a)
trace.push(acc);
}
return { result: acc, trace, steps: b, formula: `${a} ^ ${b}` };
}
let selectedOp = 'add';
document.querySelectorAll('.op-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.op-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
selectedOp = btn.dataset.op;
});
});
document.getElementById('compute').addEventListener('click', () => {
const a = Math.max(0, Math.min(9, parseInt(document.getElementById('inputA').value) || 0));
const b = Math.max(0, Math.min(9, parseInt(document.getElementById('inputB').value) || 0));
let data;
if (selectedOp === 'add') data = prAdd(a, b);
else if (selectedOp === 'mul') data = prMul(a, b);
else data = prExp(a, b);
document.getElementById('result-label').textContent = data.formula + ' =';
document.getElementById('result-val').textContent = data.result;
// Trace of running totals
const traceEl = document.getElementById('step-trace');
const stepsLabel = selectedOp === 'add'
? fmt('Outermost loop: %b% step(s) of succ', { b: b })
: selectedOp === 'mul'
? fmt('Outermost loop: %b% step(s) of add', { b: b })
: fmt('Outermost loop: %b% step(s) of mul', { b: b });
let stepsHtml = `<b>${stepsLabel}</b><br>`;
data.trace.forEach((v, i) => {
const cls = (i === data.trace.length - 1) ? 'step final' : 'step';
stepsHtml += `<span class="${cls}">${v}</span>`;
if (i < data.trace.length - 1) stepsHtml += ' → ';
});
traceEl.innerHTML = stepsHtml;
// Explanation of the loop depth
const explainEl = document.getElementById('loop-explain');
const loopDepth = { add: 1, mul: 2, exp: 3 }[selectedOp];
const loopCount = selectedOp === 'add' ? b
: selectedOp === 'mul' ? `${a}×${b} = ${a*b}`
: b;
explainEl.innerHTML =
fmt('Loop depth: <b>%loopDepth%</b> nested for-loop(s). Outer iterations: <b>%loopCount%</b>. Total computable with bounded loops — primitive recursive ✓', { loopDepth: loopDepth, loopCount: loopCount });
document.getElementById('result-box').classList.remove('hidden');
console.log('op:', selectedOp, 'a:', a, 'b:', b, '→', data.result,
'| | trace:', data.trace.join(' → '));
});
<p class="hint">Each operation is built by stacking one more bounded for-loop on top of the previous one. Choose two numbers and an operation, then click <b>Compute</b> to see how many loop steps run.</p>
<div class="controls">
<label>a = <input id="inputA" type="number" value="3" min="0" max="9" /></label>
<label>b = <input id="inputB" type="number" value="4" min="0" max="9" /></label>
<div class="ops">
<button class="op-btn active" data-op="add">a + b</button>
<button class="op-btn" data-op="mul">a × b</button>
<button class="op-btn" data-op="exp">a ^ b</button>
</div>
<button id="compute">Compute</button>
</div>
<div id="result-box" class="result-box hidden">
<div class="result-label" id="result-label"></div>
<div class="result-val" id="result-val"></div>
<div class="step-trace" id="step-trace"></div>
<div class="loop-explain" id="loop-explain"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.controls { display: flex; flex-direction: column; gap: .6rem; }
label { font-size: .95rem; display: flex; align-items: center; gap: .4rem; }
input[type=number] { width: 56px; padding: .25rem .4rem; font-size: 1rem;
border: 1px solid #ccc; border-radius: 6px; text-align: center; }
.ops { display: flex; gap: .4rem; flex-wrap: wrap; }
.op-btn { font: 600 14px system-ui; padding: .38rem .75rem; border: 2px solid #1d3557;
background: #fff; color: #1d3557; border-radius: 8px; cursor: pointer; transition: .15s; }
.op-btn.active { background: #1d3557; color: #fff; }
#compute { font: 600 14px system-ui; padding: .45rem 1rem; background: #e63946;
color: #fff; border: none; border-radius: 8px; cursor: pointer; align-self: flex-start; }
.result-box { margin-top: .9rem; padding: .8rem 1rem; border-radius: 10px;
background: #eef4fb; border: 1px solid #c8daea; }
.result-box.hidden { display: none; }
.result-label { font-size: .8rem; color: #556; text-transform: uppercase;
letter-spacing: .05em; margin-bottom: .2rem; }
.result-val { font-size: 2rem; font-weight: 700; color: #1d3557; line-height: 1.2; }
.step-trace { margin-top: .6rem; font-size: .85rem; color: #334; line-height: 1.6; }
.step { display: inline-block; background: #d0e4f7; border-radius: 4px;
padding: 0 .4em; margin: .1em; font-family: ui-monospace, monospace; }
.step.final { background: #1d3557; color: #fff; }
.loop-explain { margin-top: .5rem; font-size: .82rem; color: #556;
border-top: 1px solid #c8daea; padding-top: .5rem; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Primitive recursive arithmetic — every operation is a bounded for-loop stacked on the one below it.
//
// zero() = 0
// succ(n) = n + 1
//
// add(a, b) — b steps of succ starting from a:
// add(a, 0) = a
// add(a, s+1) = succ(add(a, s))
//
// mul(a, b) — b steps of "add a" starting from 0:
// mul(a, 0) = 0
// mul(a, s+1) = add(mul(a, s), a)
//
// exp(a, b) — b steps of "mul by a" starting from 1:
// exp(a, 0) = 1
// exp(a, s+1) = mul(exp(a, s), a)
function prAdd(a, b) {
const trace = [a];
let acc = a;
for (let step = 0; step < b; step++) {
acc = acc + 1; // succ
trace.push(acc);
}
return { result: acc, trace, steps: b, formula: `${a} + ${b}` };
}
function prMul(a, b) {
const trace = [0];
let acc = 0;
let totalSteps = 0;
for (let step = 0; step < b; step++) {
acc = acc + a; // add(acc, a) — itself b sub-steps but we count outer loops here
totalSteps += a; // inner add calls a steps
trace.push(acc);
}
return { result: acc, trace, steps: b, innerSteps: totalSteps, formula: `${a} × ${b}` };
}
function prExp(a, b) {
const trace = [1];
let acc = 1;
let totalMulSteps = 0;
for (let step = 0; step < b; step++) {
totalMulSteps += step === 0 ? a : acc; // mul(acc, a) loops
acc = acc * a; // mul(acc, a)
trace.push(acc);
}
return { result: acc, trace, steps: b, formula: `${a} ^ ${b}` };
}
let selectedOp = 'add';
document.querySelectorAll('.op-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.op-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
selectedOp = btn.dataset.op;
});
});
document.getElementById('compute').addEventListener('click', () => {
const a = Math.max(0, Math.min(9, parseInt(document.getElementById('inputA').value) || 0));
const b = Math.max(0, Math.min(9, parseInt(document.getElementById('inputB').value) || 0));
let data;
if (selectedOp === 'add') data = prAdd(a, b);
else if (selectedOp === 'mul') data = prMul(a, b);
else data = prExp(a, b);
document.getElementById('result-label').textContent = data.formula + ' =';
document.getElementById('result-val').textContent = data.result;
// Trace of running totals
const traceEl = document.getElementById('step-trace');
const stepsLabel = selectedOp === 'add'
? fmt('Outermost loop: %b% step(s) of succ', { b: b })
: selectedOp === 'mul'
? fmt('Outermost loop: %b% step(s) of add', { b: b })
: fmt('Outermost loop: %b% step(s) of mul', { b: b });
let stepsHtml = `<b>${stepsLabel}</b><br>`;
data.trace.forEach((v, i) => {
const cls = (i === data.trace.length - 1) ? 'step final' : 'step';
stepsHtml += `<span class="${cls}">${v}</span>`;
if (i < data.trace.length - 1) stepsHtml += ' → ';
});
traceEl.innerHTML = stepsHtml;
// Explanation of the loop depth
const explainEl = document.getElementById('loop-explain');
const loopDepth = { add: 1, mul: 2, exp: 3 }[selectedOp];
const loopCount = selectedOp === 'add' ? b
: selectedOp === 'mul' ? `${a}×${b} = ${a*b}`
: b;
explainEl.innerHTML =
fmt('Loop depth: <b>%loopDepth%</b> nested for-loop(s). Outer iterations: <b>%loopCount%</b>. Total computable with bounded loops — primitive recursive ✓', { loopDepth: loopDepth, loopCount: loopCount });
document.getElementById('result-box').classList.remove('hidden');
console.log('op:', selectedOp, 'a:', a, 'b:', b, '→', data.result,
'| | trace:', data.trace.join(' → '));
});
Pick two numbers and choose an operation. The demo reveals how many loop steps run under the hood — addition takes b steps, multiplication takes a⋅b steps, and exponentiation takes ab steps. The loop counts grow fast, but they are always bounded before the computation starts. That is exactly what "primitive recursive" means.
Comments
Loading comments...