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:

nickname:

enter digit "four": (this is a spam protection)

offensive comments or those I don't like will be deleted


back to homepage

RSS