Tuesday, January 6, 2026

Functional programming in JavaScript (11)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what Functional Programming is about
  2. Part 2 - Functional Pipelines
  3. Part 3 - the Y Combinator
  4. Part 4 - Monads
  5. Part 5 - Trampoline
  6. Part 6 - Lenses
  7. Part 7 - Church encoding (booleans)
  8. Part 8 - Church encoding (arithmetics)
  9. Part 9 - Church encoding (if, recursion)
  10. Part 10 - Church encoding (lists)
  11. Part 11 - Church encoding (strings, objects)

Having lists in our toolbox, we can move forward to strings and objects.

What's a string? A list of characters. We could use two auxiliary functions:

// convert JS string to Church list of characters
const toChurchString = (str) => {
  return str.split('').reverse().reduce((acc, char) => {
    // Convert char to its code, then to a Church Numeral
    const code = char.charCodeAt(0);
    const churchNum = (f => x => {
      let res = x;
      for(let i=0; i<code; i++) res = f(res);
      return res;
    });
    return LIST_NODE(churchNum)(acc);
  }, NIL);
};

// convert Church list back to JS string
const fromChurchString = (cStr) => {
  let result = "";
  let current = cStr;
  
  while(toInt(IS_NIL(current)) === 0) { 
    const charCode = toInt(HEAD(current));
    result += String.fromCharCode(charCode);
    current = TAIL(current);
  }
  return result;
};

String operations are then now just list operations, e.g.:

const APPEND = Y(self => list1 => list2 =>
  IF (IS_NIL(list1))
    (() => list2)
    (() => LIST_NODE(HEAD(list1))(self(TAIL(list1))(list2))));

const str1 = toChurchString("Hello ");
const str2 = toChurchString("world");
const combined = APPEND(str1)(str2);

console.log(fromChurchString(combined)); // "Hello world"

Checking if two string are equal is just checking if two lists are equal. The algorithm would be as follows:

  1. if both lists are empty - equal
  2. if one empty, the other not empty - not equal
  3. if both not empty - compare heads and call recursively for tail comparison
const LIST_EQ = Y(self => l1 => l2 =>
  IF (IS_NIL(l1))
    // If l1 is empty, they are equal only if l2 is also empty
    (() => IS_NIL(l2))
    // If l1 is not empty...
    (() => IF (IS_NIL(l2))
      // ...but l2 is empty, they are not equal
      (() => FALSE)
      // Both are non-empty: Compare heads and recurse on tails
      (() => AND 
        (EQUALS(HEAD(l1))(HEAD(l2))) // Heads must match
        (self(TAIL(l1))(TAIL(l2))) // Tails must match
      )
    ));

console.log( toBool(LIST_EQ(toChurchString('foo'))(toChurchString('foo')) ) ); // true
console.log( toBool(LIST_EQ(toChurchString('foo'))(toChurchString('bar')) ) ); // false

How to have objects?

First approach would be to have a list of pairs, label (field name) and value. We could even have numbers as labels (to simplify the example).

Assuming 1 is NAME and 2 is AGE, a list:

PAIR(PAIR(AGE)(25))(PAIR(PAIR(NAME)(72))(NIL))

would conceptually represent and object { "age" : "25", "name" : "72" }.

Getting a value from the object is just then yet another way of traversing the list:

const NAME = ONE;
const AGE  = TWO;
const CITY = THREE;

// A helper to make creating entries easier: (Key, Value)
const ENTRY = k => v => PAIR(k)(v);

// Create the "Object": { AGE: 3, NAME: 1 }
const user = LIST_NODE(ENTRY(AGE)(THREE))
            (LIST_NODE(ENTRY(NAME)(ONE))
            (NIL));

Querying the object is just yet another version of list traversal:

const GET = Y(self => key => list =>
  IF (IS_NIL(list))
    (() => FALSE) // Base case: not found
    (() => 
      // We "extract" the head and pass it into a function 
      // that acts as our scope for 'entry'
      (entry => 
        IF (EQUALS(key)(FIRST(entry)))
          (() => SECOND(entry)) // Found it!
          (() => self(key)(TAIL(list))) // Keep looking
      )(HEAD(list)) // This is where 'entry' is bound
    )
);

// Querying the object
const ageResult  = GET(AGE)(user);
const nameResult = GET(NAME)(user);

console.log("User Age:", toInt(ageResult)); // 3
console.log("User Name ID:", toInt(nameResult)); // 1   

In second approach, an object could just be a function that gets a key and returns a value. Adding new keys would just mean wrapping existing function inside the new function:

// A "Church Object" is a function that takes a key 'k'
const EMPTY_OBJ = k => FALSE;

// To add a property, we wrap the old object in a new function
const SET = obj => key => value => 
  requestedKey => 
    IF (EQUALS(requestedKey)(key))
      (() => value)
      (() => obj(requestedKey));

const user_2 = SET(SET(EMPTY_OBJ)(NAME)(ONE))(AGE)(THREE);

// To "get" a value, you just call the object with the key!
const name2 = user_2(NAME); 
const age2  = user_2(AGE); 

console.log("Pure Functional Object - Name:", toInt(name2)); // 1
console.log("Pure Functional Object - Age:", toInt(age2)); // 3

This feels like the ultimate, functional approach to objects. No lists, no loops to search, just nested chain of closures, each new SET just creates a closure that "remembers" the key/value and an original object inside!

If a new object could also "peek" in an existing object for a value, so that a new object possibly points to another one, its prototype, we could have a functional approach to prototypal inheritance. It's just a step ahead of what we did here!

How to have methods that can possibly access the this parameter? Well, we can put functions in objects (as fields) and when we call the function, we call the functtion passing the owning object to the function!

// another label (field name)
const DOGAGE = FOUR;
// a const
const SEVEN  = SUCC(SUCC(SUCC(FOUR)));

// A Method that calculates 'Age in Dog Years'
// It expects 'self' so it can look up 'AGE'
// like: (this) => this.age * 7
const get_dog_age_f = self => MUL(self(AGE))(SEVEN);

const user_3 = SET(SET(EMPTY_OBJ)(DOGAGE)(get_dog_age_f))(AGE)(THREE);

// send "message" (key) to the object which expects to call the function passing the object to
// the function
const SEND = obj => key => obj(key)(obj);

// --- Execution ---
// we "SEND" the DOGAGE message (field name) to the object
// this calls the function, passing the object as "self" to it!
const result = SEND(user_3)(DOGAGE);

console.log("Dog Years:", toInt(result)); // 21 (3 * 7)

Note that with one extra argument, we could call a method from an object, passing another possibly unrelated object as this! Does it sound familiar?

One of the last features of the encoding we could cover would be the let binding and while loop.

Let's start with LET. In languages where memory can be given a label, you can store arbitrary intermediate results in variables:

let x = 3;
let y = foo();
...

What such assignment is really about?

Take a look: let x = 3; do something with x - it actually means that you have to "do something with x given x is 3"!

In other words:

let x = VALUE;
do something with x;

is in fact:

(x => do something with x)(VALUE)

!

We can then define LET as:

// LET = value => body => body(value)
const LET = v => f => f(v);

and use it:

// x = 4
// y = 7
// return x + y
console.log( 
  toInt( 
    LET(FOUR)(x => 
      LET(SEVEN)(y => 
        ADD(x)(y)
      )
    ) ) );

Now, consider what a while loop is about - it's just a recursive function over a state! Is has:

  • a condition - it recursively loops until the condition is falsified
  • a transformer - it transforms the state to a new state
  • an initial state

A straight implementation would be then:

const WHILE = Y(self => condition => transform => state =>
  IF (condition(state))
    // if true: execute the transformation and recursively call in new state
    (() => self(condition)(transform)(transform(state)))
    // if false: return state
    (() => state)
);

This allows us to have:

const condition = n => NOT(IS_ZERO(n));
const transform = n => PRED(n);
const initial   = FIVE;

const result = WHILE(condition)(transform)(initial);

console.log( toInt( result ) );

This works but it's quite disappointing. It's great that while returns a value but the value is always bound to falsified condition, in this example case, WHILE returns 0 no matter what it "does" internally.

How to make it usable? Well, with an idea:

  1. initial state is a pair: (index, accumulator)
  2. condition works in the index, possibly just checks if it's equal to ZERO
  3. transform transforms the pair (index, accumulator) to a new pair (index', accumulator')

This way, the SECOND element of the returned pair is the accumulator - the actual "result" of the loop!

Let's try it on an example. Let's write a WHILE loop that sums values from 5 to 0:

const condition = pair => NOT(IS_ZERO(FIRST(pair))); // index != 0
const transform = 
  pair =>                            
    LET(FIRST(pair))(i => 
      LET(SECOND(pair))(a =>
        PAIR(PRED(i))(ADD(i)(a))));
const initial   = PAIR(FIVE)(ZERO);

const result = WHILE(condition)(transform)(initial);

console.log( toInt( SECOND(result) ) ); // 15

We can now refactor it to a single function:

const SUM_0TON = n => 
  SECOND( // return accumulator from (index, accumulator)
    WHILE
      (pair => NOT(IS_ZERO(FIRST(pair)))) // while index != 0
      (pair =>                            // take (index, accumulator)
        LET(FIRST(pair))(i =>             // i = index
          LET(SECOND(pair))(a =>          // a = accumulator
            PAIR(PRED(i))(ADD(i)(a))))    // return (i-1, a+i)
      )
      (PAIR(n)(ZERO)) // initial state: (index, accumulator) = (n,0)
  );
console.log( toInt( SUM_0TON(FIVE) ) ); // 15

Why bother looping over pairs every time? Let's have FOR that separates looping over index from transforming the accumulator!

const FOR = init_i => step => init_acc => body => 
  SECOND(
    WHILE
      (pair => NOT(IS_ZERO(FIRST(pair))))
      (pair => 
        LET(FIRST(pair))(index =>
          LET(SECOND(pair))(acc =>
          PAIR(step(index))(body(acc)(index))
          )
        )
      )
      (PAIR(init_i)(init_acc))
  );

// start with n, loop to 0 with PRED
// accumulate i+acc on every step
const SUM_0TON_2 = n => FOR(n)(PRED)(ZERO)(acc => i => ADD(acc)(i)); 

console.log( toInt( SUM_0TON_2(FIVE) ) );

If it bothers you that the FOR condition always expects that loop index is ZERO, let's abstract that too:

const FOR2 = init_i => end_i => step => init_acc => body => 
  SECOND(
    WHILE
      (pair => NOT(EQUALS(end_i)(FIRST(pair))))
      (pair => 
        LET(FIRST(pair))(index =>
          LET(SECOND(pair))(acc =>
          PAIR(step(index))(body(acc)(index))
          )
        )
      )
      (PAIR(init_i)(init_acc))
  );

// from n to ZERO desc
const SUM_0TON_3 = n => FOR2(n)(ZERO)(PRED)(ZERO)(acc => i => ADD(acc)(i));
console.log( toInt( SUM_0TON_3(FIVE) ) );

// from 0 to n+1 (not including n+1)
const SUM_0TON_4 = n => FOR2(ZERO)(SUCC(n))(SUCC)(ZERO)(acc => i => ADD(acc)(i));
console.log( toInt( SUM_0TON_4(FIVE) ) );

which can be further refactored to

const FOR3 = init_i => cond => step => init_acc => body => 
  SECOND(
    WHILE
      (pair => cond(FIRST(pair)))
      (pair => 
        LET(FIRST(pair))(index =>
          LET(SECOND(pair))(acc =>
          PAIR(step(index))(body(acc)(index))
          )
        )
      )
      (PAIR(init_i)(init_acc))
  );

// acc = 0
// for i=0; i<n; i++
//   acc = acc + i
const SUM_0TON_5 = n => FOR3(ZERO)(i => LEQ(i)(n))(SUCC)(ZERO)(acc => i => ADD(acc)(i));
console.log( toInt( SUM_0TON_5(FIVE) ) );

With booleans, numbers, strings, objects, if, variables and loops, we are free to code whatever we want!

A short summary of what we've covered:

Concept Standard programming Church encoding
Booleans true, false x=>y=>x, x=>y=>y
Numbers 3 f => x => f(f(f(x)))
Branching if (p) { a } else { b } p(a)(b)
Variables let x = 3; doWork(x); LET(3)(x => doWork(x))
Loops initial; while (condition) { transform }; WHILE(condition)(transform)(initial)
Loops for (initial; condition; loop transform ) { accumulator transform } FOR(intial)(loop transform)(condition)(accumulator transform)
Lists/Arrays [1, 2] PAIR(1)(PAIR(2)(NIL)) (almost :), it was in fact slightly more complicated to make it work in JS)
Objects obj.prop obj(KEY)
Methods obj.doWork() SEND(obj)(DO_WORK)

You can continue on your own from here:

  1. create more complicated methods that possibly have more arguments
  2. make your field names not numbers but strings
  3. add the EXTEND function that takes an existing object and creates a new one with the given set as prototype
  4. find out how negative numbers or floating point numbers can be encoded

Happy coding!

Saturday, January 3, 2026

Functional programming in JavaScript (10)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what Functional Programming is about
  2. Part 2 - Functional Pipelines
  3. Part 3 - the Y Combinator
  4. Part 4 - Monads
  5. Part 5 - Trampoline
  6. Part 6 - Lenses
  7. Part 7 - Church encoding (booleans)
  8. Part 8 - Church encoding (arithmetics)
  9. Part 9 - Church encoding (if, recursion)
  10. Part 10 - Church encoding (lists)
  11. Part 11 - Church encoding (strings, objects)

In the next blog entry, we continue the Church encoding of various language elements into JavaScript. We already did booleans, numbers, IF and recursion.

This time we'll do lists.

Lists can be implemented using pairs, a list of (1,2,3,4) is just (1, (2, (3, 4))), nested pairs. Other complex objects can be built on top of this idea.

If you need PAIR/FIRST/SECOND, just go back to the previous blog entry.

// NIL is a pair where the first element is TRUE (a flag for "I am empty")
const NIL    = PAIR(TRUE)(TRUE); 
const IS_NIL = l => FIRST(l); // Returns TRUE if it's NIL, FALSE if it's a real PAIR

// Re-defining PAIR for numbers to ensure FIRST(l) is always FALSE
const LIST_NODE = x => y => PAIR(FALSE)(PAIR(x)(y));
const HEAD = l => FIRST(SECOND(l));
const TAIL = l => SECOND(SECOND(l));

And that's it! We can define a list and an example operation on a list:

// Building the list: [1, 2, 3]
const list1 = LIST_NODE(ONE)(NIL);
const list2 = LIST_NODE(TWO)(list1);
const list3 = LIST_NODE(THREE)(list2); // This is [3, 2, 1]

// --- sum function 
const SUMLIST = Y(self => list => 
  IF (IS_NIL(list))
    (() => ZERO) 
    (() => ADD(HEAD(list))(self(TAIL(list)))));

console.log( toInt(SUMLIST(list3)) );

We could also have an auxiliary toArray function, similar to previous toInt and toBool:

const toArray = (list, elementConverter = (x => x)) => {
  const result = [];
  let current = list;

  // While IS_NIL is FALSE (using toInt to bridge the Church Boolean to JS)
  while (toInt(IS_NIL(current)) === 0) {
    // 1. Get the data from the current node
    const churchValue = HEAD(current);
    
    // 2. Convert and push to our JS array
    result.push(elementConverter(churchValue));
    
    // 3. Move to the next node
    current = TAIL(current);
  }
  
  return result;
};

console.log( toArray( list3, toInt ) ); // [3,2,1]

How about MAP or other list operations that just transform list to another list?

Well, using all the tools we have, it's easy:

const MAP = Y(self => f => list =>
  IF (IS_NIL(list))
    (() => NIL)
    (() => LIST_NODE(f(HEAD(list)))(self(f)(TAIL(list)))));

// Example: Double every number in the list
const DOUBLE = n => ADD(n)(n);
const doubledList = MAP(DOUBLE)(list3); // [6, 4, 2]

console.log("First item doubled:", toInt(HEAD(doubledList))); // 6
console.log("Total:", toInt(SUMLIST(doubledList)));           // 12

And that's it. You can continue from here on your own. Define other operators, write more high level functions. Experiment.

Just to give you some more ideas:

const LEQ = m => n => IS_ZERO(SUB(m)(n));  // no negative numbers, subtracting from ZERO stays at ZERO!
const GT = m => n => NOT(LEQ(m)(n));
const LT = m => n => NOT(LEQ(n)(m));

console.log( toBool( LEQ(TWO)(FOUR)) ); // true
console.log( toBool( LEQ(FOUR)(TWO)) ); // false
console.log( toBool( LEQ(TWO)(TWO)) );  // true
console.log( toBool( LT(TWO)(FOUR)));   // true
console.log( toBool( LT(FOUR)(TWO)));   // false
console.log( toBool( LT(FOUR)(TWO)) );  // false

A complete code of all examples of Church encoding:

const TRUE  = a => b => a;
const FALSE = a => b => b;

const AND = p => q => p(q)(p);
const OR  = p => q => p(p)(q);
const NOT = p => p(FALSE)(TRUE);

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

console.log( toBool( TRUE ) );
console.log( toBool( FALSE ) );

console.log( toBool( AND(TRUE)(TRUE) ) );
console.log( toBool( AND(FALSE)(TRUE) ) );
console.log( toBool( AND(TRUE)(FALSE) ) );
console.log( toBool( AND(FALSE)(FALSE) ) );

console.log( toBool( OR(TRUE)(TRUE) ) );
console.log( toBool( OR(FALSE)(TRUE) ) );
console.log( toBool( OR(TRUE)(FALSE) ) );
console.log( toBool( OR(FALSE)(FALSE) ) );

console.log( toBool( NOT(TRUE) ) );
console.log( toBool( NOT(FALSE) ) );

const ZERO  = f => x => x;
const ONE   = f => x => f(x);
const TWO   = f => x => f(f(x));
const THREE = f => x => f(f(f(x)));

const toInt = n => n(i => i + 1)(0);

console.log( toInt( ZERO ) );
console.log( toInt( ONE ) );
console.log( toInt( TWO ) );
console.log( toInt( THREE ) );

const SUCC  = n => f => x => f(n(f)(x));

const FOUR = SUCC(THREE);
console.log( toInt( FOUR ) );

const ADD   = m => n => f => x => m(f)(n(f)(x));
const MUL   = m => n => f => m(n(f));

console.log( toInt( ADD(TWO)(THREE) ) );
console.log( toInt( MUL(TWO)(THREE) ) );

//const POW = m => n => n(m);
const POW = m => n => f => n(m)(f);

console.log( toInt( POW(TWO)(FOUR) ) );

// --- lists
const PAIR   = x => y => f => f(x)(y);
const FIRST  = p => p(TRUE);
const SECOND = p => p(FALSE);

// A helper that takes a pair (n, n+1) and returns (n+1, n+2)
const PHI = p => PAIR(SECOND(p))(SUCC(SECOND(p)));

// PRED starts at (0, 0), applies PHI 'n' times, then takes the FIRST element
const PRED = n => FIRST(n(PHI)(PAIR(ZERO)(ZERO)));

// SUBTRACT(m)(n) is just applying PRED to m, n times.
const SUB = m => n => n(PRED)(m);

console.log( toInt( PRED(FOUR) ) );
console.log( toInt( SUB(FOUR)(TWO) ) );
console.log( toInt( SUB(TWO)(FOUR) ) );

// --- if
const IF = p => a => b => p(a)(b)();

// --- recursion
const Y = f => (x => x(x))(x => f(y => x(x)(y)));

const IS_ZERO = n => n(x => FALSE)(TRUE);

const EQUALS = m => n => 
  AND (IS_ZERO(SUB(m)(n))) 
      (IS_ZERO(SUB(n)(m)));

// --- THE FAC FUNCTION ---
const FAC = Y(self => n =>
  IF (EQUALS(n)(ONE))               // if n == 1
    (() => ONE)                     //    return 1
    (() => MUL(n)(self(PRED(n))))); //    else return n * fac(n-1)

console.log( toInt( FAC(FOUR) ) );

// NIL is a pair where the first element is TRUE (a flag for "I am empty")
const NIL    = PAIR(TRUE)(TRUE); 
const IS_NIL = l => FIRST(l); // Returns TRUE if it's NIL, FALSE if it's a real PAIR

// Re-defining PAIR for numbers to ensure FIRST(l) is always FALSE
const LIST_NODE = x => y => PAIR(FALSE)(PAIR(x)(y));
const HEAD = l => FIRST(SECOND(l));
const TAIL = l => SECOND(SECOND(l));

// Building the list: [1, 2, 3]
const list1 = LIST_NODE(ONE)(NIL);
const list2 = LIST_NODE(TWO)(list1);
const list3 = LIST_NODE(THREE)(list2); // This is [3, 2, 1]

// --- sum list
const SUMLIST = Y(self => list => 
  IF (IS_NIL(list))
    (() => ZERO) 
    (() => ADD(HEAD(list))(self(TAIL(list)))));

console.log( toInt(SUMLIST(list3)) );

const toArray = (list, elementConverter = (x => x)) => {
  const result = [];
  let current = list;

  // While IS_NIL is FALSE (using toInt to bridge the Church Boolean to JS)
  while (toInt(IS_NIL(current)) === 0) {
    // 1. Get the data from the current node
    const churchValue = HEAD(current);
    
    // 2. Convert and push to our JS array
    result.push(elementConverter(churchValue));
    
    // 3. Move to the next node
    current = TAIL(current);
  }
  
  return result;
};

console.log( toArray( list3, toInt ) ); // [3,2,1]

// --- map
const MAP = Y(self => f => list =>
  IF (IS_NIL(list))
    (() => NIL)
    (() => LIST_NODE(f(HEAD(list)))(self(f)(TAIL(list)))));

// Example: Double every number in the list
const DOUBLE = n => ADD(n)(n);
const doubledList = MAP(DOUBLE)(list3); // [6, 4, 2]

console.log("First item doubled:", toInt(HEAD(doubledList))); // 6
console.log("Total:", toInt(SUMLIST(doubledList))); // 6

const LEQ = m => n => IS_ZERO(SUB(m)(n));  // no negative numbers, subtracting from ZERO stays at ZERO!
const GT = m => n => NOT(LEQ(m)(n));
const LT = m => n => NOT(LEQ(n)(m));

console.log( toBool( LEQ(TWO)(FOUR)) ); // true
console.log( toBool( LEQ(FOUR)(TWO)) ); // false
console.log( toBool( LEQ(TWO)(TWO)) );  // true
console.log( toBool( LT(TWO)(FOUR)));   // true
console.log( toBool( LT(FOUR)(TWO)));   // false
console.log( toBool( LT(FOUR)(TWO)) );  // false

Happy coding!

Functional programming in JavaScript (9)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what Functional Programming is about
  2. Part 2 - Functional Pipelines
  3. Part 3 - the Y Combinator
  4. Part 4 - Monads
  5. Part 5 - Trampoline
  6. Part 6 - Lenses
  7. Part 7 - Church encoding (booleans)
  8. Part 8 - Church encoding (arithmetics)
  9. Part 9 - Church encoding (if, recursion)
  10. Part 10 - Church encoding (lists)
  11. Part 11 - Church encoding (strings, objects)

We continue our Church encoding of numbers. In the last post we've defined ADD/MUL/POW.

How do we define SUB?

To have SUB, we have to have PRED, the predecessor function. How do we get this?

Turns out, this is tricky. SUCC was about applying a function. How do you un-apply a function?

The solution found by Stephen Klenee in 30s involves pairs and so called sliding window.

Start with a pair of (0,0). Then apply function (a,b) => (b, b+1) to it n times. Then take first element of the pair.

Got it? Let's try for 3. (0,0) -> (0,1) -> (1,2) -> (2,3). First element of the result pair is 2.

To have this in our encoding system, we define some helper functions and then, the PRED. With PRED, we can have SUB:

// --- pairs
const PAIR   = x => y => f => f(x)(y);
const FIRST  = p => p(TRUE);
const SECOND = p => p(FALSE);

// A helper that takes a pair (a,b) and returns (b, b+1)
const PHI = p => PAIR(SECOND(p))(SUCC(SECOND(p)));

// PRED starts at (0, 0), applies PHI 'n' times, then takes the FIRST element
const PRED = n => FIRST(n(PHI)(PAIR(ZERO)(ZERO)));

// SUBTRACT(m)(n) is just applying PRED to m, n times.
const SUB = m => n => n(PRED)(m);

console.log( toInt( PRED(FOUR) ) );
console.log( toInt( SUB(FOUR)(TWO) ) );

We are finally ready to have IF and recursion (using the Y discussed eariler). Note that IF forces an extra computation of its result with (). This is because of the eager evaluation of JavaScript that will force us to define both IF branches as functions (that have to be evaluated). We also define the IS_ZERO predicate and EQUALS (using IS_ZERO and SUB):

// --- IF, with additional ()
const IF = p => a => b => p(a)(b)();

// --- Y (recursion)
const Y = f => (x => x(x))(x => f(y => x(x)(y)));

const IS_ZERO = n => n(x => FALSE)(TRUE);

const EQUALS = m => n => 
  AND (IS_ZERO(SUB(m)(n))) 
      (IS_ZERO(SUB(n)(m)));

And here comes the big moment. We can now use all building blocks together to actually implement a high level recursive function:

// --- factorial!
const FAC = Y(self => n =>
  IF (EQUALS(n)(ONE))               // if n == 1
    (() => ONE)                     //    return 1
    (() => MUL(n)(self(PRED(n))))); //    else return n * fac(n-1)

console.log( toInt( FAC(FOUR) ) );

Wow, that was a long journey! We started from simple blocks, TRUE/FALSE, we added boolean operators, numbers, arithmetic, recursion and voila, we have built a system that's capable of expressing non-trivial computation of factorials!

If you just thought "it kind of looks like LISP" then you're on the right track. Conceptually, these two, Church encoding and LISP are similar. Technically, they differ much as LISP goes forward in optimizing how things are represented. For example, to represent 1000 in our encoding, we nest function application 1000 times. LISP supports numbers as values and performs arithmetics directly. Also, here we represent everything as functions, LISP represents everything as lists (S-expressions).

While at mathematical level, both are close, you could say that LISP is a Church encoding optimized to make it a practical programming language. One of possible topics for future blog posts in this series is a simple LISP interpreter in JavaScript.

Next time we'll discuss lists.

Functional programming in JavaScript (8)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what Functional Programming is about
  2. Part 2 - Functional Pipelines
  3. Part 3 - the Y Combinator
  4. Part 4 - Monads
  5. Part 5 - Trampoline
  6. Part 6 - Lenses
  7. Part 7 - Church encoding (booleans)
  8. Part 8 - Church encoding (arithmetics)
  9. Part 9 - Church encoding (if, recursion)
  10. Part 10 - Church encoding (lists)
  11. Part 11 - Church encoding (strings, objects)

We continue the previous post on Church encoding. Please make sure you follow by quickly reading the basics.

So let's encode natural numbers. The idea here is to apply a function n-times to encode a natural number n. An auxiliray function to visualise numbers is exacly like "apply i => i+1 to 0 n times". Note how unusual this encoding is - numbers are not just "values", they are "processes". THREE is not just 3, it's a function applied 3 times.

const ZERO  = f => x => x;
const ONE   = f => x => f(x);
const TWO   = f => x => f(f(x));
const THREE = f => x => f(f(f(x)));

const toInt = n => n(i => i + 1)(0);

console.log( toInt( ZERO ) );
console.log( toInt( ONE ) );
console.log( toInt( TWO ) );
console.log( toInt( THREE ) );

ZERO, ONE, TWO, THREE, great but how do we continue? How to define a SUCC function that just computes n+1 from given n? That should be easy: "apply a function n times and just once again":

const SUCC  = n => f => x => f(n(f)(x));

const FOUR = SUCC(THREE);
console.log( toInt( FOUR ) );

Since this could be tricky here, how the SUCC actually work, let's try to symbolically perform some example computations. Everytime you feel unsafe later on, you can verify presented definitions in a similar way.

Let's check what's SUCC(ZERO).

SUCC is n => f => x => f(n(f)(x))

Here, n looks like a function since it's used in a term n(f). And indeed, since ZERO is f => x => x, we have:

SUCC(ZERO) = (n => f => x => f(n(f)(x))(f => x => x)

There seem to be a variable name collision and to avoid it, we rename variables in f => x => x to a => b => b (or anything else not containing n, f and x)

SUCC(ZERO) = (n => f => x => f(n(f)(x))(a => b => b) = (we substitute a => b => b for n) = ...

          ... = f => x => f( (a => b => b)(f)(x) ) = f => x => f( (b => b)(x) ) = f => x => f(x) = ONE

How do we define ADD and MUL? Well, ADD is "apply function n times and then m times". MUL is "apply n times (apply m times)".

const ADD   = m => n => f => x => m(f)(n(f)(x));
const MUL   = m => n => f => m(n(f));

console.log( toInt( ADD(TWO)(THREE) ) );
console.log( toInt( MUL(TWO)(THREE) ) );

That's impressive! POW is even simpler, we just apply m to n:

const POW = m => n => n(m);

console.log( toInt( POW(TWO)(FOUR) ) );

and if this one bothers you as too similar to MUL, you can rewrite it to make it explicit:

const POW = m => n => f => n(m)(f);

console.log( toInt( POW(TWO)(FOUR) ) );

Slow down and carefully consult the difference between MUL and POW.

Ready to continue? In the next post we'll define SUB and write our first complex function.

Functional programming in JavaScript (7)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what Functional Programming is about
  2. Part 2 - Functional Pipelines
  3. Part 3 - the Y Combinator
  4. Part 4 - Monads
  5. Part 5 - Trampoline
  6. Part 6 - Lenses
  7. Part 7 - Church encoding (booleans)
  8. Part 8 - Church encoding (arithmetics)
  9. Part 9 - Church encoding (if, recursion)
  10. Part 10 - Church encoding (lists)
  11. Part 11 - Church encoding (strings, objects)

One of the most beautiful examples of functional approach, that opens eyes and helps to understand basics, is the Church encoding. It's basically a way to encode operations and data types as functions; Church encoding encodes things in lambda calculus but here we are going to work with JavaScript.

Why is this even interesting? Encoding functions (operations) as functions sounds obvious. But encoding data types - booleans, numbers, lists (arrays/objects) as functions - basically means that we can build a world of computations that only contains functions, nothng else! It's not efficient, you wouldn't use it for real-world applications but it's really simple and has a really powerfull property: it's Turing complete which means that:

  • you can store data (it has paris/lists)
  • it does arithmetic (add/sub)
  • it can make decisions (it has Booleans/IF)
  • it can loop (or recurse infinitely) (it has the Y combinator)

Following the Church encoding in JavaScript is straightforward, at least at first. There are some technical difficulties later on, when IF is implemented. Before we begin, let's discuss this.

The IF will be a function of three arguments, IF(Condition)(ResultA)(ResultB). In some functional languages (like Haskell), function arguments are evaluated lazily, only if needed. When Haskell sees function arguments and some of them are not used in function definition, Haskell doesn't even try to evaluate them.

JavaScript instead computes arguments eagerly, all arguments are evaluated before the function is executed. If there's a recursion (possibly with another IF), JavaScript does a work that's not required, risking an infinite recursion.

Because of that, the Church encoding of some elements will differ in JavaScript comparing to how you'd encode things in Haskell.

Anyway, let's get started from basic building blocks.

const TRUE  = a => b => a;
const FALSE = a => b => b;

const AND = p => q => p(q)(p);
const OR  = p => q => p(p)(q);
const NOT = p => p(FALSE)(TRUE);

If you are new to this, that could be quite surprising, seeing even true and false encoded as functions. More, they are defined in a specific way, always taking one argument and possibly returning another function of one argument.

Is there a way to map TRUE and FALSE to plain JavaScript booleans, so that we can test and see how things work here? Well, just define a simple helper:

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

and you can write few tests:

console.log( toBool( TRUE ) );
console.log( toBool( FALSE ) );

console.log( toBool( AND(TRUE)(TRUE) ) );
console.log( toBool( AND(FALSE)(TRUE) ) );
console.log( toBool( AND(TRUE)(FALSE) ) );
console.log( toBool( AND(FALSE)(FALSE) ) );

console.log( toBool( OR(TRUE)(TRUE) ) );
console.log( toBool( OR(FALSE)(TRUE) ) );
console.log( toBool( OR(TRUE)(FALSE) ) );
console.log( toBool( OR(FALSE)(FALSE) ) );

console.log( toBool( NOT(TRUE) ) );
console.log( toBool( NOT(FALSE) ) );

If you need some intuition: TRUE is a function of two arguments that just discards the second (try to think about TRUE as (a,b) => a) and FALSE is a function that takes two arguments and discards the first ((a,b) => b). Let's try to rewrite few of above examples then:

AND(TRUE)(TRUE) = T(T)(T) (because of how AND is defined) = T (because T discards second argument)

AND(TRUE)(FALSE) = T(F)(T) (because of how AND is defined) = F (because T discards second argument)

AND(FALSE)(TRUE) = F(T)(F) (because of how AND is defined) = F (because F discards first argument)

AND(FALSE)(FALSE) = F(F)(F) (because of how AND is defined) = F (because F discards first argument)

You can follow and rewrite OR and NOT, make sure you follow before moving forward. We'll continue in next post.

Tuesday, December 30, 2025

Functional programming in JavaScript (6)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what Functional Programming is about
  2. Part 2 - Functional Pipelines
  3. Part 3 - the Y Combinator
  4. Part 4 - Monads
  5. Part 5 - Trampoline
  6. Part 6 - Lenses
  7. Part 7 - Church encoding (booleans)
  8. Part 8 - Church encoding (arithmetics)
  9. Part 9 - Church encoding (if, recursion)
  10. Part 10 - Church encoding (lists)
  11. Part 11 - Church encoding (strings, objects)

State management is one of fundamental topics. Consider a state:

let state = { 
  user: { 
    age: 35,
    address: { 
      city: 'Warsaw' } 
    } 
  };

When state changes, an usual way of modifying it would be to just overwrite a part of it:

state.user.address.city = 'Prague';

In the functional world, updating an object is considered a bad practice. Instead, in a functional approach the state would be immutable which means that instead of a local modification, a new state is created. There are good reasons for that:

  • a complete new state means that there is no chance of race condition issues in a concurrent environment
  • a state change history can be tracked which allows so called time travel debugging

Because of this, it is common to encourage immutable states and if you ever studied/used React, you certainly saw something like:

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age':
      return { ...state, user: { ...state.user, age: state.user.age + 1 } };
    case 'changed_city':
      return { ...state, user: { ...state.user, address: { city: action.new_name } } };
    default:
      throw Error('Unknown action: ' + action.type);
  }
}

console.log( JSON.stringify( state ) );
state = reducer(state, { type: 'changed_city', new_name: 'Prague' } );
console.log( JSON.stringify( state ) );

Note how inconvenient is to create a new state when a deeply nested property is modified. The JavaScript's spread operator (...) helps a bit but still, imagine having dozens of such statemens in your code where modified props are deep down in the state. People often call this the Spread Operator Hell.

A functional approach to this problem, where a new state is to be created from existing state in an atomic way, are Lenses. A Lens lets us have a functional getter and setter (for a property, for an index of an array) and focuses on just a tiny portion of a possibly huge data. A Lens has two operations:

  • view - gets the value the lens focuses on
  • over - applies a given function to the value the lens focuses on
const lens = (getter, setter) => ({
  view: (obj) => getter(obj),
  over: (fn, obj) => setter(fn(getter(obj)), obj)
});

Simple? To view a value, you use the getter. To modify, you use the gettter to get the value, apply a function over it (that's why it's called over) and use setter to set the value back.

We can define our first lens, the Prop lens or Prop-based lens as it takes a property name of an object:

const prop = (key) => lens(
  (obj) => obj[key],                      // The Getter
  (val, obj) => ({ ...obj, [key]: val })  // The Setter (immutable!)
);

Still simple? Should be, JavaScript helps us here as both getting and setting a property of an object, given the property's name, is supported by the language.

And, that's it. Let's see the example. It composes three property lenses into a new lens that focuses on a property deep down in the state:

const userL    = prop('user');
const addressL = prop('address');
const cityL    = prop('city');

// compose them to focus deep into the state
const userCityLens = {
  view: (obj)     => cityL.view(addressL.view(userL.view(obj))),
  over: (fn, obj) => userL.over(u => addressL.over(a => cityL.over(fn, a), u), obj)
};

const currentCity = userCityLens.view(state); 
console.log( currentCity );
state = userCityLens.over(z => 'Prague', state);
console.log( JSON.stringify( state ) );

Take a close look of how the composition is done and make sure you are comfortable with the order of view/over application in the composed lens.

An interesting feature of the lens is that the function can actually operate on the original value (just like an ordinary property setter; lens just does it using a function!) e.g.:

state = userCityLens.over(z => z.toUpperCase(), state);

Still, isn't such manual composition kind of disappointing? Let's see what can be improved here - creating a new lens from existing lenses should be automated!

Well, here it is:

const compose2Lenses = (l1, l2) => ({
  // To view: go through l1, then l2
  view: (obj) => l2.view(l1.view(obj)),  
  // To update: l1.over wraps the result of l2.over
  over: (fn, obj) => l1.over(target => l2.over(fn, target), obj)
});

// A variadic version to compose any number of lenses
const composeLenses = (...lenses) => lenses.reduce(compose2Lenses);

Note how we start by combining just two lenses and then we make a variadic version that just combines an arbitrary number of lenses.

The composed lens can be now defined as:

const userCityLens = composeLenses(userL, addressL, cityL);

Nice! Ready for another step forward? How about replacing the Prop-based lens that takes a property name with another lens, let's call it the Path-based lens (or Selector-based lens) that just takes an arrow function that points to the exact spot in the state object we want to focus on? So we could have:

const userCityLens = selectorLens( s => s.user.address.city );

Compare the two, which one looks better and feels more understandable? I'd prefer the latter. However, to have it, we'd have to somehow parse the given arrow function definition so that the lens is able to learn that it has to follow this path: user -> address -> city. Sounds difficult?

Well, it is. There are two approaches. One would be to stringify the function and parse it to retrieve the path. Another one, preferable, would be to use the JavaScript's Proxy to record the path by just running the function over an object! I really like the latter approach:

const tracePath = (selector) => {
  const path = [];
  const proxy = new Proxy({}, {
    get(_, prop) {
      path.push(prop);
      return proxy; 
    }
  });
  selector(proxy);
  return path;
};

const setterFromPath = (path, fn, obj) => {
  if (path.length === 0) return fn(obj);
  const [head, ...tail] = path;
  return {
    ...obj,
    [head]: setterFromPath(tail, fn, obj[head])
  };
};

const selectorLens = (selector) => {
  const path = tracePath(selector);
  return {
    view: (obj) => selector(obj),
    over: (fn, obj) => setterFromPath(path, fn, obj)
  };
};

A bit of explanation.

First, the tracePath is supposed to get a function and execute it over an empty object. Assuming the function is an arrow function like s => s.user.address.city, the getter will be called 3 times and the path ['user', 'address', 'city'] would be recorded.

Second, the setterFromPath is just a recursive way of applying a function over a path to a given object.

And last, the lens just uses the two auxiliary functions to define view and over.

And how it's used? Take a look:

const userCityLens = selectorLens( s => s.user.address.city );

const currentCity = userCityLens.view(state);
console.log( currentCity );
state = userCityLens.over(z => "Prague", state);
console.log( JSON.stringify( state ) );

And yes, it works! That ends the example.

There are other interesting lenses, like the Prism which handles the case of possible non-existence of the data, in a way similar to the Maybe monad. It's similar to the conditional getter built into the language:

const city = state?.user?.address?.city;

but the interesting thing about the Prism is that it handles both the getter and the setter, while the built-in ?. operator cannot be used at left side of the assignment.

Friday, December 19, 2025

Functional programming in JavaScript (5)

Please take a look at other posts about functional programming in JavaScript:

  1. Part 1 - what Functional Programming is about
  2. Part 2 - Functional Pipelines
  3. Part 3 - the Y Combinator
  4. Part 4 - Monads
  5. Part 5 - Trampoline
  6. Part 6 - Lenses
  7. Part 7 - Church encoding (booleans)
  8. Part 8 - Church encoding (arithmetics)
  9. Part 9 - Church encoding (if, recursion)
  10. Part 10 - Church encoding (lists)
  11. Part 11 - Church encoding (strings, objects)

Another interesting technique that is commonly used in functional programming is the Trampoline. Is a hybrid way of avoiding stack overflows that possibly occur during deep recursive computations.

Functional environments that support Tail Call Optimizations can possibly go deep into recursion. But in V8's JavaScript, stack depth is limited by memory. Since each stack frame consumes a portion of this memory and the stack memory limit is around 1M, depending on how many actual arguments your stack calls have, you can possibly execute about 10000 simple recursive calls. In case of heavy calls (with multiple arguments), this goes down quickly.

Consider this example:

function naiveFac(n) {
  return n <= 1 ? 1 : n * naiveFac(n - 1);
}
console.log( naiveFac(5) );
console.log( naiveFac(10000) );

On my current V8 (node 24), the former computes 120 correctly, the latter throws:

Uncaught RangeError RangeError: Maximum call stack size exceeded
    at naiveFac (c:\Temp\app.js:2:3)

And this is where the Trampoline can be used.

But first, let's refactor this to the Accumulator Passing Style we've already covered:

function naiveFac2(n) {
   return (function factStep(n, accumulator = 1) {
      return n <= 1
         ? accumulator
         : factStep(n-1, n*accumulator);
      })(n);
}
console.log( naiveFac2(5) );
console.log( naiveFac2(10000) );

It still fails for 10000 but the important refactorization is already there.

Now, consider the Trampoline:

function trampoline(fn) {
  while (typeof fn === 'function') {
    fn = fn();
  }
  return fn;
}

First, note that it's imperative, it uses while. And yes, it's not pure, not quite functional but it does the job in a hybrid world of JavaScript. Then, note what it does. It gets a function, executes it and if the return value of the function is another function, it executes it again. And again, until something else than a function is returned. Do you get the idea? Our recursive function, instead just calling itself, will now return a function that calls it but the Trampoline will execute this new function. It avoids recursion but also requires this specific approach that involves the accumulator.

We can now go back to the original issue and refactor it to use the Trampoline:


function trampolineFac(n) {
   function factStep(n, accumulator = 1n) {
      return n <= 1n 
        ? accumulator
        : () => factStep(n-1n, n*accumulator);
   }
   return trampoline( () => factStep(BigInt(n)) );
}

console.log(trampolineFac(5));  
console.log(trampolineFac(10000)); 

Note that it requires some subtle tweaks to actually support BigInts as the result is a huge number. But, hey, it works! The recursion limit is gone!