Day 3

Day 3: JavaScript Surprise Interlude

This article was written quickly and lazily. If any of my statements seems dubious that’s probably because they are.

Goal Rambling

It would be cool to get a JS environment running on my site like Eloquent Javascript does or something but this is a last-minute cop out since I was unexpectedly busy today.

Maybe that’ll be on tomorrow’s blog improvements TODO list.

Cop Out

Functional Programming

If you’re not already a functioning1programming enthusiast, the following paragraphs might turn you off it. I recommend reading the relevant chapter of Eloquent Javascript2 instead.

JavaScript has first class functions, meaning that functions are like any other objects: we can pass them around, return them, assign values and all that fun stuff.

With that comes some functional ideas, like those of map() and filter(). Simply put, map() allows us to take some collection and apply a function to each element. Here is a classic, uninspired example:

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

Notice that the function takes another function as a parameter and since JS is cool we can define it inline with => instead of writing

1
2
3
const double = function(x) {
  return 2 * x;
};

Similarly, filter() also does pretty much what is advertised. We have some function that returns a boolean (true or false) then it keeps the elements in the collection for which true is returned. Another uninspired example:

1
[1,2,3].map(x => x<3) // [1,2]

Wow.

The Problem

So I’ve been working on [REDACTED].

The NDA

I signed an NDA for work and while I don’t think writing about what I did is even remotely problematic it’s too late (at night) to ask for permission so I’m going to keep things extremely vague. If anyone from Slide is reading this please don’t pursue legal action.

The Problem

Anyway, as I was saying, this was relevant today. I’m trying to come up with a cutesy analogy to my problem but I’m having a hard time so I guess this’ll be boring.3

Oh, I had another idea. Never mind, it was atrocious. I actually started writing this time, if you’re interested here is a footnote.4

I have a list of ids for resources in a database. Extracting all of the documents is a slow process and as such I’d like to keep it to a minimum. Documents fall into either category A or B, exhaustively, and determining that is also a length process. Depending on whether a document is in A or B I need to do something different with it. I could write something like

1
2
3
4
5
6
7
8
9
documents = database.get_things_slowly();
for(const document in documents) {
	if (figure_out_type_slowly(document) == 'A') {
			thing1(document);
	}
	else {
			thing2(document);
	}
}

but we’re functional programmers here so that’s beneath us. I’d much rather write

1
2
documents = database.get_things_slowly();
documents.filter(document => figure_out_type_slowly(document) == 'A').map(thing1);

and then… oh wait. To figure out which documents are type B I need the rest of the collection, which is not easily accessible now that we’ve banished it to the filtered-out void. A second filter could solve the problem for us

1
2
...
documents.filter(document => figure_out_type_slowly(document) == 'B').map(thing2);

but now we’re just duplicating effort. We could also do some sort of list comparison but that also feels bad, once we know which documents are type A we should know the type B documents automatically. A filter-else pattern of sorts.

This brings us to the black sheep5 of the functional family:

Reduce

Unlike filter() and map() which return arrays, reduce() can return anything. The catch is that it has two arguments:

  1. The reducing function, which takes in the current element but also the combined value from the previous elements, the accumulator
  2. and a starting value for the accumulator.

The cliche example is a taking the sum of a list:

1
[1,2,3].reduce((acc, current) => acc + current, 0) // 6

To break it down, acc is the accumulator and 0 is it’s starting value. As we progress through the array6 we have

  1. acc = 0, current = 1 -> acc = 1
  2. acc = 1, current = 2 -> acc = 3
  3. acc = 3, current = 3 -> acc = 6

We can use reduce() to rewrite the map() then filter() example as a single step:

1
2
3
4
5
documents.filter(document => figure_out_type_slowly(document) == 'A').map(thing1);
documents.reduce( (acc, doc) =>
	if (figure_out_type_slowly(curr) == 'A') {
		acc.push(thing1(doc))
	}, []);

When a document meets the condition we do thing1() to it and add it to the empty array, the same as doing thing1() to all documents that meet a condition.

Astute readers will realize that we’re now handling the condition internally with an if. Thus, we can extend it to include the other case as follows:

1
2
3
4
5
6
7
8
documents.filter(document => figure_out_type_slowly(document) == 'A').map(thing1);
documents.reduce( (acc, doc) =>
	if (figure_out_type_slowly(curr) == 'A') {
		acc.push(thing1(doc))
	}
	else {
		acc.push(thing2(doc))
	}, []);

I’m sure this would be a more interesting read with a concrete example but bear with me one step further. We don’t have to keep the accumulator as a simple array, it can be an object with multiple fields. I’m not going to include an example since I really should be asleep right now.

Reflection

JavaScript is not entirely repulsive . This was my first time doing something practical in JS that required more than 3 lines of code.7 There are several ways of approaching any given problem and some of them look far cooler than others.8

Magic

The functional programming experience was very empowering, I felt like a JavaScript wizard. Until it didn’t work…9

Despite having found a fix to the latest of a long series of problems, I realized how fragile magical construction can be and discovered that an entirely mundane for-loop could accomplish the same thing while remaining readable.

I rewrote my function but I’ll never forget what it was like to be all-powerful.

Tomorrow?

Tomorrow I’ll actually work on my blog. I’d promise that but I shouldn’t make promises I can’t keep.


  1. This is a typo, I meant “functional.” At this late hour of the night I find it amusing so it’s staying. ↩︎

  2. Disclosure: I am not paid to shill EJS, I just like it. ↩︎

  3. My brain really wanted to write a scenario about trees with names so this is for the best. ↩︎

  4. I have a large PDF I am determined to search as inefficiently as possible so whenever I need to look at pages in it I print the entire thing. Today, I’m looking for pages featuring good ideas for blog posts. Thankfully, whenever I have a good idea I write “good blog idea” at the top of the page so it’s relatively easy to extract them. However, there is too much content on each page to do process it while I’m skimming through. Once I have found the good ideas, I burn the rest of the pages, as I always do. I’ve decided that I need to actually learn some CSS so I’ll read through the pages I found and instead of committing to any project I’ll just note down the learning resources on each one. Thinking about learning CSS makes everything else seem… (I gave up here) ↩︎

  5. This is dramatic but it is very underappreciated in my uneducated opinion. ↩︎

  6. Note that reduce starts from the left by default and there is a reduceRight function if you’re not into that. ↩︎

  7. I’m actually not sure if this is an exaggeration. ↩︎

  8. Disclaimer: I have been advised that this is not a good metric for code quality. ↩︎

  9. The issue was that I had interspersed async calls, specifically for the database logic. This can be dealt with via a chain or promises (painful) or letting async infect everything via await (less painful). ↩︎


Interactive Graph