Skip to content Skip to sidebar Skip to footer

Inject Functions With Side-effects

I'm having an issue when using higher-order functions. Let's say I have the following code that doesn't use them (instead call global functions): import {db_insert} from 'some-db-l

Solution 1:

I'm having an issue when using higher-order functions. Let's say I have the following code that doesn't use them (instead call global functions):

c = (x) =>console.log(x)
b = (x) =>c(x)
a = (x) =>b(x)
a('Hello world')

But this is a terrible starting point, to be honest

  • c is just an eta conversion of console.log
  • b is just an eta conversion of c
  • a is just an eta conversion of b

In other words, a === b === c === console.log – If you're going to understand higher-order functions, you need a better starting point


The common example: map

People love to demonstrate higher-order functions using Array.prototype.map

constf = x => x + 1constg = x => x * 2const xs = [1,2,3]

console.log (xs.map (f)) // [2,3,4]console.log (xs.map (g)) // [2,4,6]

What's happening here? It's actually pretty neat. We can take an input array xs and create a new array where each element is the transformation of an element in xs using a higher-order function. The higher-order function could be a one-time use lambda, or it could be a named function that was already defined elsewhere.

// xs.map(f)[ f(1), f(2), f(3) ][   2 ,   3 ,   4  ]// xs.map(g)[ g(1), g(2), g(3) ][   2 ,   4 ,   6  ]// xs.map(x => x * x)[ (x => x * x)(1), (x => x * x)(2), (x => x * x)(3) ][              1 ,              4 ,              9  ]

The bigger picture

OK, so that's a very practical example of using higher-order functions in JavaScript, but ...

Am I missing something?

Yes. Higher-order functions have an immensely deep and meaningful power. Let me pose another set of questions:

  • What if there was no such thing as an Array?
  • How would we group values together in a meaningful way?
  • Without a group of values, surely we couldn't map over them, right?

What if I told you only functions were required to do all of it?

// emptyconst empty = nullconstisEmpty = x => x === empty

// pairconstcons = (x,y) => f => f (x,y)
constcar = p => p ((x,y) => x)
constcdr = p => p ((x,y) => y)

// listconstlist = (x,...xs) =>
  x === undefined ? empty : cons (x, list (...xs))
constmap = (f,xs) =>
  isEmpty (xs) ? empty : cons (f (car (xs)), map (f, cdr (xs)))
const list2str = (xs, acc = '( ') =>
  isEmpty (xs) ? acc + ')' : list2str (cdr (xs), acc + car (xs) + ' ')

// generic functionsconstf = x => x + 1constg = x => x * 2// your dataconst data = list (1, 2, 3)

console.log (list2str (map (f, data)))          // '( 2 3 4 )'console.log (list2str (map (g, data)))          // '( 2 4 6 )'console.log (list2str (map (x => x * x, data))) // '( 1 4 9 )'

I you look closely, you'll see this code doesn't use any native data structures provided by JavaScript (with the exception of Number for example data and String for purposes of outputting something to see). No Objects, no Arrays. No tricks. Just Functions.

How does it do that? Where is the data?

In short, the data exists in partially applied functions. Let's focus on one specific piece of code so I can show you what I mean

constcons = (x,y) => f => f (x,y)
constcar = p => p ((x,y) => x)
constcdr = p => p ((x,y) => y)

const pair = cons (1,2)
console.log (car (pair)) // 1console.log (cdr (pair)) // 2

When we create pair using cons(1,2) look carefully how the data gets stored in pair. What form is it in? cons returns a function with x and y bound to the values 1 and 2. This function, we'll call it p, is waiting to be called with another function f, which will apply f to x and y. car and cdr provide that function (f) and return the desired value – in the case of car, x is selected. in the case of cdr, y is selected.

So let's repeat...


"Am I missing something?"

Yes. What you just witnessed with the genesis of a versatile data structure out of nothing but (higher-order) functions.

Is your language missing a particular data structure you might need? Does your language offer first class functions? If you answered 'yes' to both of those, there is no problem because you can materialize the particular data structure you need using nothing but functions.

That is the power of higher-order functions.


Reluctantly convinced

OK, so maybe you're thinking I pulled some tricks there making a replacement for Array above. I assure you, there are no tricks involved.

Here's the API we will make for a new dictionary type, dict

dict (key1, value1, key2, value2, ...) --> d
read (d, key1) --> value1
read (d, key2) --> value2

write (d, key3, value3) --> d'
read (d', key3) --> value3

Below, I will keep the same promise of not using anything except functions (and strings for demo output purposes), but this time I will implement a different data structure that can hold key/value pairs. You can read values, write new values, and overwrite existing value based on a key.

This will reinforce the concept of higher-order data, that is data that is an abstraction of a lower abstraction – ie, dict is implemented using node which is implemented using list which is implemented using cons, etc

// emptyconst empty = nullconstisEmpty = x => x === empty

// pairconstcons = (x,y) => f => f (x,y)
constcar = p => p ((x,y) => x)
constcdr = p => p ((x,y) => y)

// listconstlist = (x,...xs) =>
  x === undefined ? empty : cons (x, list (...xs))
constcadr = p => car (cdr (p))
constcddr = p => cdr (cdr (p))
constcaddr = p => car (cddr (p))
constcadddr = p => cadr (cddr (p))

// nodeconstnode = (key, value, left = empty, right = empty) =>
  list (key, value, left, right)
const key = car
const value = cadr
const left = caddr
const right = cadddr

// dictconstdict = (k,v,...rest) =>
  v === undefined ? empty : write (dict (...rest), k, v)

constread = (t = empty, k) =>
  isEmpty (t)
    ? undefined
    : k < key (t)
      ? read (left (t), k)
      : k > key (t)
        ? read (right (t), k)
        : value (t)

constwrite = (t = empty, k, v) =>
  isEmpty (t)
    ? node (k, v)
    : k < key (t)
      ? node (key (t), value (t), write (left (t), k, v), right (t))
      : k > key (t)
        ? node (key (t), value (t), left (t), write (right (t), k, v))
        : node (k, v, left (t), right (t))

let d = dict ('a', 1, 'b', 2)
console.log (read (d, 'a')) // 1console.log (read (d, 'b')) // 2console.log (read (d, 'c')) // undefined

d = write (d, 'c', 3)
console.log (read (d, 'c')) // 3

Surely now you see the power of higher-order functions, right ? ^_^

Post a Comment for "Inject Functions With Side-effects"