Search code examples
boolean-logiccombinators

Boolean SKI logic


I'm trying to grok the combinators, so I wrote the basic ones: K and S in Typescript:

const K: Kestrel = a => b => a;
const S: Starling = a => b => c => a(c)(b(c));

Then, I followed the Wikipedia article about SKI Boolean Logic:

TRUE = K
FALSE = SK

therefore:

const TRUE: True = K;
const FALSE = S(K);

And this is passing the basic tests:

const t = () => true;
const f = () => false;

test('Ktf = (TRUE)tf = t', () => {
  const actual = TRUE(t)(f)();
  expect(actual).toBe(K(t)(f)());
  expect(actual).toBe(true);
});

test('SKxy = y', () => {
  expect(S(K)(t)(f)()).toBe(false);
  expect(FALSE(t)(f)()).toBe(false);
});

So far so good, but when it comes to NOT Wikipedia says:

NOT = (FALSE)(TRUE) = (SK)(K)
(TRUE)NOT = TRUE(FALSE)(TRUE) = FALSE
(FALSE)NOT = FALSE(FALSE)(TRUE) = TRUE

I wrote a code and the tests and I got:

const NOT = S(K)(K);

test('(TRUE)NOT = FALSE', () => {
  expect(TRUE(NOT)(t)(f)()).toBe(false); // <- see these () after (t)(f)?
  expect(TRUE(NOT)(f)(t)()).toBe(true);
});

test('(FALSE)NOT = TRUE', () => {
  expect(FALSE(NOT)(t)(f)).toBe(true); // <- here we don't have these ()
  expect(FALSE(NOT)(f)(t)).toBe(false);
});

My question is about the two lines that I highlighted with comments: why to make the tests pass, I need this asymmetry between TRUE and FALSE? Seems like I followed all the details in K and S implementation, but in the case of TRUE(NOT)(t)(f) I get a function () => false, and in the case of FALSE(NOT)(t)(f) I get a false.

Is there any detail I missed or Wikipedia is not precise enough in definitions?

P.S.: Here is the code: https://repl.it/repls/FuchsiaFuchsiaTruetype


Solution

  • Two things, 1. your implementation of NOT is wrong, and 2. the asymmetry is purely a coincidence.

    1. the coincidence:

    Note that both TRUE and FALSE are curry functions that are waiting for two more args to evaluate.

    Now check this fact:

    FALSE(NOT)(t) === t
    

    FALSE effectively doesn't care about 1st arg, and all it does is to simply return the 2nd arg. So you get t as return value, and when you do FALSE(NOT)(t)(f), you're effectively calling t(f) where f is useless arg, and it yields true. Thus the coincidence.

    2. implementation of NOT

    Wikipedia page writes NOT as postfix operator, but that's just a literal expression they use to write down the idea. In JS code you cannot write the same.

    (T)NOT = T(F)(T) = F

    See that NOT is expanded as two args to be passed to the function before it. To implement this idea in JS, you need to write:

    const NOT = b => b(S(K))(K);
    

    and reverse the calling signature back to normal form: NOT(TRUE).

    I modified your original code here: https://repl.it/@hackape/KnowledgeableWealthyDowngrade