2021.02.19 05:38
church encoding in javascript
Some time ago, just for fun, I tried to implement Church encoding of natural numbers in JavaScript. Here it is:
const zero = (func, start) => start
const successor = n => ((func, start) => func(n(func, start)))
const one = successor(zero)
const two = successor(one)
const three = successor(two)
const stars = num => num(str => str + '*', '')
const add = (a, b) => a(successor, b)
const multiply = (a, b) => a(x => add(x, b), zero)
const trueVal = (a, b) => a
const falseVal = (a, b) => b
const boolToString = b => b('true', 'false')
const isZero = (num) => num(_ => falseVal, trueVal)
const and = (a, b) => a(b(trueVal, falseVal), falseVal)
const or = (a, b) => a(trueVal, b(trueVal, falseVal))
const not = (a) => a(falseVal, trueVal)
const pair = (a, b) => (selector => selector(a, b))
const predecessor = num => {
// each pair has two elements: a number and its successor
const reachNumberByPairs = num => num(
previousPair => {
const isFirstPair = and(isZero(previousPair(trueVal)), isZero(previousPair(falseVal)))
return isFirstPair(pair(zero, one), pair(successor(previousPair(trueVal)), successor(previousPair(falseVal))))
},
pair(zero, zero)
)
return reachNumberByPairs(num)(trueVal)
}
const subtract = (a, b) => b(predecessor, a)
// This obviously won't work: forget about recursion if you don't have lazy evaluation.
/*
const isLess = (a, b) => and(isZero(a), isZero(b))(
falseVal,
isZero(a)(
trueVal,
isZero(
b,
isLess(predecessor(a), predecessor(b))
)
)
)
*/
const loopWithStop = (times, doWhat, checkStop, initialState) => times(
previousPair => {
const previousState = previousPair(trueVal)
const previousCheckStop = previousPair(falseVal)
return previousCheckStop(
previousPair,
(() => {
const shouldStop = checkStop(previousState)
const newState = doWhat(previousState)
return pair(newState, shouldStop)
})()
)
},
pair(initialState, falseVal)
)
const isEqual = (a, b) => {
const checkEqual = (a, b) => loopWithStop(
add(one, add(a, b)), // if I don't add this one, if both arguments are zero, the loop won't run at all and we won't know they're equal
previousState => {
const countA = previousState(trueVal)(trueVal)
const countB = previousState(trueVal)(falseVal)
const equal = and(isZero(countA), isZero(countB))
return pair(pair(predecessor(countA), predecessor(countB)), equal)
},
previousState => {
const countA = previousState(trueVal)(trueVal)
const countB = previousState(trueVal)(falseVal)
return or(isZero(countA), isZero(countB))
},
pair(pair(a, b), falseVal)
)
return checkEqual(a, b)(trueVal)(falseVal)
}
const isGreater = (a, b) => and(
not(isEqual(a, b)),
isZero(subtract(b, a))
)
const isLess = (a, b) => and(
not(isEqual(a, b)),
isZero(subtract(a, b))
)
const divideWithRemainder = (dividend, divisor) => {
const findQuotientAndRemainder = (dividend, divisor) => loopWithStop(
dividend,
previousState => {
const countDividend = previousState(trueVal)(trueVal)
const divisor = previousState(trueVal)(falseVal)
const currentQuotient = previousState(falseVal)(trueVal)
const countDividendLessThanDivisor = isLess(countDividend, divisor)
return countDividendLessThanDivisor(
pair(pair(countDividend, divisor), pair(currentQuotient, countDividend)),
pair(pair(subtract(countDividend, divisor), divisor), pair(successor(currentQuotient), zero))
)
},
previousState => {
const dividend = previousState(trueVal)(trueVal)
const divisor = previousState(trueVal)(falseVal)
return isLess(dividend, divisor)
},
pair(pair(dividend, divisor), pair(zero, zero))
)
return findQuotientAndRemainder(dividend, divisor)(trueVal)(falseVal)
}
const isPrime = num => {
return predecessor(predecessor(num))(
currentPair => {
const currentDivisor = currentPair(trueVal)
const currentResult = currentPair(falseVal)
const notDivisible = not(isZero(divideWithRemainder(num, currentDivisor)(falseVal)))
const newResult = and(currentResult, notDivisible)
return pair(successor(currentDivisor), newResult)
},
pair(two, trueVal)
)(falseVal)
}
// this is where the actual code ends and the testing begins
console.log(stars(three))
const five = add(two, three)
console.log(stars(five))
console.log(stars(multiply(three, five)))
console.log(boolToString(trueVal))
console.log(boolToString(falseVal))
console.log(boolToString(isZero(zero)))
console.log(boolToString(isZero(three)))
console.log(boolToString(and(isZero(zero), isZero(zero))))
console.log(boolToString(and(isZero(zero), isZero(three))))
console.log(boolToString(or(isZero(zero), isZero(three))))
console.log('-----')
const myPair = pair(3, 5)
console.log(myPair(trueVal))
console.log(myPair(falseVal))
console.log(stars(predecessor(five)))
console.log(stars(subtract(five, two)))
console.log(boolToString(isEqual(zero, three)))
console.log(boolToString(isGreater(three, zero)))
console.log(boolToString(isGreater(three, three)))
console.log(boolToString(isLess(three, three)))
console.log(boolToString(isLess(two, three)))
console.log(boolToString(isLess(five, three)))
const toNum = num => num(x => x + 1, 0)
const result = divideWithRemainder(five, three)
const quotient = result(trueVal)
const remainder = result(falseVal)
console.log(toNum(quotient))
console.log(toNum(remainder))
console.log(boolToString(isPrime(add(five, one))))
console.log(boolToString(isPrime(add(five, two))))
comments:
back to homepage
RSS