Inject Functions With Side-effects
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 ofconsole.log
b
is just an eta conversion ofc
a
is just an eta conversion ofb
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 Object
s, no Array
s. No tricks. Just Function
s.
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"