Why Would Anyone Need JavaScript Generators?

Introduction

James Sinclair begins by addressing the self-evident nature of the talk's title and suggests a more appropriate one: "Why Would Anyone Care About JavaScript Generator Functions?" He aims to provide two compelling reasons: writing more efficient and seemingly impossible code using generators.

What is a Generator Function?

Instead of a formal definition, James uses a code example to explain generator functions. He highlights the use of the "function*" keyword, the "yield" keyword that suspends execution and returns values, and the distinct behavior of the "return" statement within generators.

Live Coding and the 'Next' Method

James dives into live coding, demonstrating the execution flow of a generator function. He shows how calling the "next" method on a generator object steps through the function's code, pausing at each "yield" statement. He also introduces the "done" property, indicating whether a generator has more values to yield.

Generators and the Iterable Protocol

The talk highlights that generators implement the iterable protocol, making them usable in "for...of" loops and with the spread operator. This allows for iterating over generated values and converting them into arrays.

Efficient Code with Generators: The Tim Tam Slam Example

Using the relatable example of the Tim Tam Slam, James demonstrates how generators can improve code efficiency. He shows how processing a sequence of actions on multiple items can be inefficient with traditional array methods and presents a solution using custom "map" and "take" functions that leverage generators' lazy evaluation.

Writing "Impossible" Code: Infinite Lists

The talk delves into the concept of creating infinitely long lists using generators. James provides an example of generating all positive integers, showcasing how generators allow for representing and working with sequences that would be impossible to store in memory conventionally.

Practical Application: Solving the Eight Queens Problem

To demonstrate a practical use of generators, James tackles the classic Eight Queens problem. He breaks down the problem, utilizes generators to generate possible solutions, and implements logic to check their validity, showcasing how generators can be used for complex problem-solving.

The Importance of Expressiveness in Code

Beyond the specifics of generators, James emphasizes the significance of having multiple ways to express solutions in code. He argues that generators, while not always necessary, provide a valuable tool for writing more readable and understandable code by enabling different approaches to problem-solving.

Beyond Laziness: Passing Values Back to Generators

The talk briefly touches upon the capability of generators to receive values back during execution using the "yield" keyword and the "next" method. This bi-directional communication allows for more dynamic and interactive use of generators.

Conclusion and Call to Action

James concludes by reiterating the key takeaways: generators' lazy evaluation and their ability to change data processing order. He encourages the audience to explore generators as a means to enhance their code's expressiveness and problem-solving capabilities.

I don't know if you've noticed, but there is a bit of a problem with the title of my talk.

And that is, the answer to this question, why would anyone need JavaScript generator functions, it's self evident, you don't.

And the reason is because I imagine that there are plenty of you in this room who have had perhaps long, successful careers as software engineers, And you've never written a single line of generator code.

And that's okay, but to Oh, my clicker's not working, so we'll use this one.

What I should have titled the talk is Why Would Anyone Care About JavaScript Generator Functions?

And my, hope today is to give you at least two reasons.

First of these is we can use generator functions to write more efficient code.

The next one is we can use generator functions to write impossible code, and I'll explain what I mean when we get there.

But before we get to those two, I thought it would be a good idea to explain what even is a generator, so that we're all on the same page.

So let's start with what is a generator function.

Now, rather than try and give you a definition, that wouldn't be very useful, what I've done is I've put together an example.

So here we have a generator function.

And it is much like any other JavaScript function, it has some, variable assignments, it has console logs, and it has a return statement at the end, but there are some differences.

My mouse out of the way, if, oh my click is being dodgy.

Let's go back.

There we go.

Alright.

You'll notice here, first of all, that we have a function star keyword.

And what that's doing, it's telling the JavaScript runtime that, hey, this isn't actually a normal JavaScript function.

It's a generator function.

The next thing you'll notice is this yield keyword.

Now, the way a generator function works is It will execute its statements one by one the same way any other JavaScript, function will execute.

But when it gets to this yield keyword, what it does is It suspends execution, it gives up that value, the first one is claim01, and it returns it up to the calling function and it stops.

That's it.

And it won't go again until something kicks it off.

And we'll have a look at that in a second.

There's finally one last thing to notice.

And that is The return statement.

Now, it looks like a normal JavaScript return statement, but it's not.

And I just wanted to highlight that because it caught me out a few times, and I don't want you to go ahead thinking, oh, getting caught the way I did.

Just be careful of the return statement in a generator function.

What I want to do is do some live coding, because I like to live dangerously.

What I'm going to do is I'm going to copy all this code into the console, and we'll expand that so you can still see it, and we'll go to the next slide, and I'm going to copy this code as well.

Now, as a thought experiment, what I want you all to do is have a think, what's going to happen when I hit the enter key?

What do you expect to see on the console?

What do you expect the value of generator to be?

Have you all got that in your head?

Thinking through.

All right, I'm going to hit enter now.

Was your expectation?

Nothing at all.

The generator function, it does do something.

We have a value here, so let's have a look at this value of generator.

So we did get something.

We got a value.

And it is an object.

It has a class generator.

If you dive down into the prototype chain, eventually We will get to some methods.

Now, the constructor, we can just ignore.

That was, that should be a reference to the function that created it.

There's all the, all these other ones.

So we've got next, we've got return, we've got throw.

They're all very interesting, but we're just gonna focus on next for today.

There are fun things you can do with return and throw, but we only have time to cover next.

So let's have a look and see what happens if we type generator.next.

What do we get?

So when we call next, oh, hang on there, we've got a console log.

You can see that when we called next, finally our function started to run.

And we didn't just get that console log, we also got a value back.

But we didn't just get the plain efficient code string, we got back an object, which had a key.

With, sorry, a property of value, which has the string we wanted.

And then we have this other property called done, which is false.

And what that's saying is that the JavaScript runtime expects there to be more values.

Now, it can get a bit tedious calling next all the time.

So what we can do is write a little function called run generator.

And what it's going to do is it's just going to do a loop, until we're done.

And it's going to console log the results.

So if I type run generator with generator, Then we get all the rest of the code runs.

But notice how the last one has a value of 42.

So there's our return statement.

That's where it comes in.

But it might not appear in the way you expect.

And that final one comes with done is true.

So that is the basics of generator functions.

That's really all you need to know.

But there's a few handy little other things they can do.

So generators, they implement the iterable protocol.

And what that means is that we can chuck it into a for of loop.

So if I've got another generator function here, which I'll call places, I can chuck that into a for of loop, and it just console logs, hello world, hello web directions, hello Sydney.

The other thing we can do with the iterable protocol, which is neat, is that it works with the spread operator.

So if I copy and paste this code into the console, Instead of getting that generator object, I get an array, and I can see what the results of spreading that generator is.

And that's handy, because I often need to inspect my code in the console, and it shows me what's going on.

Now at this point you might be thinking, so what James, like, all you've really done so far is show us that generators are a little bit like arrays, in that you can loop over them.

They're just a bit awkward, really.

So what is that we can do with generators that might actually be useful.

So let's have a look at writing some efficient code.

Now, to illustrate this, I am going to use the example of the Tim Tam Slam, because we are in Sydney, in Australia, so I'm hoping you're all familiar with this cultural institution, which is the Tim Tam Slam.

And the way a Tim Tam Slam works is that you take a biscuit, like one of these from a packet, you bite a corner off each, opposite sides, You put one end into your hot beverage, Milo is, of course, traditional, but it can be any hot beverage.

You draw liquid through it like it's a straw, and as soon as the liquid hits your mouth, you put the whole thing in, and it explodes, and it's glorious and wonderful, and you keep going until you either feel physically ill or there are no more Tim Tams.

All right, now if we were to write that as code, it might look something like this.

So I've got a function for each step of that process.

So I've got one for biting off one corner, one for biting off the other corner, one for dipping it in the beverage, one for drawing the liquid, turning the mouse.

You get the idea.

But, lest you think this is just illustrative, theoretical teaching code, I am going to run this code, and it goes something like this.

So we have our Tim Tam, which I've drawn by hand, it, both the corners off, we put it in the beverage, and then we have one happy chappy.

But what happens if we have a whole packet of biscuits?

Let's say we have 11 biscuits, and we've put them in an array, because Tim Tams have 11 in a packet, a sort of understandable approach that we could take that wouldn't work is to use, array methods.

We could map over this with our functions that we had earlier.

So we could take the array of biscuits, we could map with bite arbitrary corner, bite opposite corner, so on and so forth.

Then at the end, we might call slice and say, let's just have five of those.

But of course, if we were to run that one, it looks like this.

So we've already got a problem.

We've started biting all the Tim Tams in the entire packet.

And it's still going, and then we end up putting them all in the mug, and when we try to draw liquid, we end up with a problem.

It's a bit of a mess.

The thing to notice about this, though, is, as I said, I don't think this is necessarily a bad way to think about the problem.

It doesn't work, but it's a good way of expressing what we want to happen.

So let's see if there's a way that we could fix that.

And we're going to do that by writing our own map method.

So this is a map method that I've written, and apologies for the currying.

I'm a functional program enthusiast, so bear with me.

So what this does is it is a function that takes a transform function as its first function, variable, parameter, sorry.

It returns a generator function.

That generator function, it accepts something that's iterable.

So it could be an array, could be a generator, we don't care, so long as it works with for of.

It's an iterable.

And what we do is we loop over that and we just yield the transformation of each item.

It's not terribly complicated, but what I'm going to do is I'm going to clear my console and we're going to put that in there.

And let's try mapping with a function.

Let's just say hello.

To save time, and we will map over places from earlier.

Now, of course when I do that we get back generator, but if we want to see what's going on, let's go back to the console, what we can do is we can spread that, right?

And then we get the result.

Hello world, hello web directions, hello Sydney.

So that's map.

Now, for our particular problem, we don't need the full power of slice.

So what we're going to do is we're going to write a different function that just It takes a maximum number of results.

So what this one does is it takes the max results as its first parameter, it returns a generated function which once again takes an iterable, and then it just sets up a little bit of state, it creates a variable called count, and it keeps going until it hits the max number and then it stops.

So we'll copy that, and so if we were to take, for example, the first two from places, then we just get world and web directions.

So far, it's fairly simple, right?

We have one more piece of the puzzle we need to fix our problem, and that is a function composition function called pipe.

Now, this is scary, it looks scary, but it's not.

All it's doing is it's taking a value and a list of functions.

Takes that value, passes it to the first function, gets the result back, passes that to the next function, gets the result back, and so on and so forth, until it runs out of functions, then it gives you back the result.

So if I copy and paste this into our console, I'll clear it up so you can all see a bit better.

Then what we can do, here's some functions that I prepared earlier.

One says say hello, the other says uLegends, and what do is we're going to put that into the console, and we get back, hello WebDirections, you legends.

So that's the pipe function.

Notice that one worked with just a plain string.

It doesn't, it's not restricted to generators, it's just a generic function of sorts.

And what we have now is the ability to put it all together into our solution.

So what we do is we take our array of biscuits, we pipe it through biting at average corner, biting opposite corner, so on, so forth, and then at the end we say let's stop after five because we might start feeling a bit sick.

Now if we run that code, let's see what happens.

It's one at a time, and it keeps going, one, two, three, four, five.

And then we stop, and like we wanted, we have six more pristine tim tams still in the packet.

Which is a good thing!

We've fixed our problem.

The question I want to ask now is, why does that work?

And it works, because generators they do nothing until you call next.

The way it works is each generator is doing the minimum amount of work necessary to get to the next yield statement, and then it will stop.

And because of that, what it does is it changes the order in which the data is processed, which we can use to our advantage.

Another way we can use that to our advantage is to write what I call impossible code, and by that I mean we can create infinitely long lists of things.

For example, what we might want to do is I don't know, hypothetically, is create a list of all the positive integers.

I don't know why you'd want to do that.

Actually, I'll show you in a second.

But what we're going to do is we're going to create a function called naturalNumbers.

It's a generator function.

It starts at zero, and it has an infinite loop in there.

And it'll keep yielding.

Just forever.

It just increments that counter until we hit number.maxSafeInt.

And if I change this so that it was bigint, it would just keep going until the computer ran out of memory.

So let's copy that, and let's just prove that it works.

Here we go.

Now I do have to be careful because this is actually an infinite loop.

So if I were to just spread this, it would crash my browser.

Believe me, I have done it.

So what we're going to do is we're going to take first.

I'm writing take first.

And let's take the first, I don't know, let's take a thousand.

And we're going to call naturalNumbers, and we're going to spread that into an erase just so we can see what's going on.

There we have all the numbers from 0 to 999.

I won't expand it, but trust me, it's there.

The other thing we can do is we can transform our infinite list.

What if, for example, this is a boring example, but bear with me, we could filter this.

Let's, create a function called filter.

It's like our other one, it's, curried, but it takes a predicate function, returns a generator function.

That takes an iterable, and we just loop over that iterable, and if we should keep an item, then we yield it.

So here is, oops, there's our filter, and what we can do with that is say, what if we want just the even numbers?

So we're going to copy and paste this, and we get a generator, which I've called evens, and if we take, say, Let's take 500 of those.

And I have the first 500 even numbers.

Now, the thing about this is, I could have done it another way.

I could have done it with map.

I could just multiply every number by 2 and I get the same result.

That would be slightly more, efficient maybe.

But then I didn't get to show you filter.

My point is, you can create lists from other lists.

And if you wanted to, you could maybe create the Fibonacci sequence, or you could create triangular numbers, or you can use something called the sieve of Erasthenes.

Try saying that quickly.

It is, and that would give you prime numbers.

And of those, I think prime numbers is probably likely the most useful.

I occasionally use prime numbers for pseudo random kind of stuff, but at the same time, these are all boring.

So I thought to myself, what could I do that would be, useful to you all.

That would maybe help you in your careers, something that's practical.

And I thought to myself, let's do a pointless coding interview question.

So what we're going to do is a problem called eight queens.

Now, the way eight queens works is you're given a chessboard and eight queens.

And you have to place each queen on the board such that no queen can attack any other piece.

And what do I mean by attack?

A queen, can attack anything in the same column, anything in the same row, or anything on the same diagonal.

I'm sure you mostly know the rules of chess.

Now, if we were to place all of the, if we count all the ways you can place eight queens on a board, you get this very large number that I'm not even going to attempt to say, which is a lot.

So let's see if we can break that down a little bit.

And what we're going to do is we're going to remember that queens can, attack in a column, which means we can never have two queens in a single column.

And, of course, we can't have them in the same row, but we're going to ignore that for the moment and just think about the column.

Now, what we could do is, for each queen, we notice that we have eight queens, so one queen per column.

And we could count how far down each queen is and give that a number.

And so if we had all the queens across the top, we would have 0, 0, 0, 0, 8 zeros.

And if we were to start moving the queens, then we get something, That looks like base 8 numbers.

And we already happen to know how to create a really long list of numbers.

We just did that just a second ago.

So what if we would use that to reduce our search space?

So then we go down from that really big number to 16, 777, 216.

Which is still a big number, but not quite as big, right?

We're making progress.

So let's write some code.

First of all, we want to figure out, how can we convert one of those plain old decimal integers into something that we can test?

So I'm going to, we're going to use a function that I've written here called natToArray, and all it's doing is it's using the toString method with a parameter.

So if you pass a parameter to a toString, you get base, whatever that parameter is.

But, point to note here, you're all going for senior engineer roles, right?

We have anticipated that they might throw a challenge at us.

What if we have a different size chessboard?

Now to handle that, we are taking as a parameter n, which gives us the size of the chessboard, which means that we, to solve our problem, we have to have our, digits in base n, whatever that happens to be.

For our case, it's going to be 8, but it might be 7, might be 6, might be more.

So we just take that digit, we parse it into a string, we pad it out with zeros, split it, and we've got our digits.

So let's try that.

Now, I'm going to try nat2array on a number I prepared earlier.

I want to start with 2.

Oops, I forgot to put the 8 in.

There we get.

0, 7, 6, 5, 4, 3, 2, 1.

So that's nat2array.

So we're making some progress and remember each of those digits is how far down a queen is.

Next, we need to figure out, how do we check if a solution is valid?

So we've got all these queens on the board.

How do we know if this solution is a good one?

And what we need to do is we check for queens in the same row, and then we check for queens on the same diagonal.

And by design, we've already ruled out queens in the same column, so we're good there.

Don't have to check that one.

So what we're going to do is we're going to use a cheeky method to check for repeats.

So what this one does is it takes a potential solution and shoves it in a set.

Now, if two queens are in the same row, they will have the same number, because they're the same distance down.

So the size of the set will be different from the size of the one that went in.

If I say, what do we do?

We just did this one.

Let's copy this, and we'll copy this.

So we've got no repeats.

Now, are there any repeats in this one?

Copy that.

There are no repeats.

It's true.

Now, if I was, for example, to say no, Repeats, we say all zeros, then we get false.

It's working, we've, confirmed that it's going alright Now what about if we're on the same diagonal?

Now this is probably the trickiest part of this problem, and I apologize, this isn't the most straightforward one.

But the thing to focus on is this bit here.

So we've got, we're comparing two queens, they've got an x coordinate and a y coordinate, and if the horizontal distance is the same as the vertical distance, then they are on the same diagonal.

Same diagonal.

The bit here is checking, oh, what if it's the same queen?

We exclude that if it's the same queen.

Now, what's tricky about this is I've written it in such a way that I use it with sum.

What I've done is I've been a bit clever with these parameters here, and what I'm going to do is say if I put in this one from earlier, what I'm going to do is I'm going to call sum and attacks diagonal.

In hindsight, I probably shouldn't have been so clever, but that's how it works.

This one is saying that there are some pieces that are on the same, diagonal So we have a way to convert from a decimal number into the format that we can check.

We now have a way to check for the same row.

We can check for the same diagonal.

We wanna put that together.

And we have one single function, which just checks for a valid solution.

So we've got is valid solution.

Now let's check if this one we had before is a valid solution by, oops.

Pasting that in.

And no, that is not a valid solution.

We will find one, but that's not it.

We've got two more things that we want to get to, before we can, put our whole solution together first is, this array kind of format that we've got is a bit harder to read.

So let's convert it into something a bit more readable so that we can get the XY coordinates.

And finally, we're only looking for one solution.

We don't, care about the others.

So we are gonna put this solution together.

So this is just converts that solution to points.

So solution to points IFV, all that does is converts it to pairs.

XY coordinates.

We'll clear that so we can see.

And finally, we've got head.

It takes an iterator, not an iterable.

An iterator just means something that has a next method.

Put that in just to check that it works.

We'll try head with places.

Whoops.

Call places.

And we get back [????], which is the first item in our iterable that we had before.

So now, we have all the tools we need to put together a solution to the nQueens problem.

So when we do that, here's our function.

So we've got a function we've called Queens.

It takes the size of the board as a parameter, and then we pipe it through a series of functions.

The first thing we pass it though is the infinite list of natural numbers.

Then we map over that and convert it into that, base 8 digit format.

Then we filter out anything that isn't a valid solution.

We map that into the nice x, y coordinate values and we take the first one.

And if we do that, let's try it out.

Let's see if it finds a solution.

It does take a second because it's doing a lot of computation, and then we get this, which is actually a valid solution, and it looks like this if we plot it out.

Now, if you're quick, you can check that I've actually mapped out the same thing, but I did check it is the same one.

So we've solved the nQueens problem using generators.

And infinite lists.

Now, at this point, you might be thinking, James, those are silly examples.

What's the point?

And I have a confession to make at this point.

And that confession is, really Sorry, my clicker is not doing the right thing.

I'm going to abandon it.

I'm going to make a confession.

And the confession is, I don't care whether or not you use generators.

Not at all.

Go ahead, use them at home, or don't.

But what I do care about is having alternate ways to express solutions to problems.

So you might have noticed that in both cases today, we had our Tim Tam solution, we had our, 8Queen solution.

Both of the cases we ended up with this nice pipeline of functions.

We do this, then this, and if you look at either of those solutions, I reckon you could give that to a junior coder and ask them, what does this code do?

And they'd be able to have some kind of a guess.

You can look at that and you can go, I figure out what's going on here.

And we were able to do that because of the possibilities that generators opened up for us.

Now generators aren't the only way we can get different ways of looking at problems, but they are one way, and they are handy.

And so what I do care about is you having better ways to express solutions to your problems.

That's what I care about.

Now, I do have to mention that generators can do more than just be lazy.

One thing they can do is, pass values back.

So they, can take a yield, and when you, yield a function, you can pass something in with next, and that goes into a variable, and you can do stuff with it.

But I don't have time to go into that today.

Instead, we're going to go for questions in just a second.

And for those of you in the room, we have some prizes.

So if, you are the first, one of the first two to ask, a question, you will get one of my books.

If you're online, I will figure out a way to get an e book to you.

Haven't forgotten you.

Things to remember.

Generators are lazy.

And that laziness can change how data is processed.

So the, remember the generator?

It does nothing until you call next.

And even then, it will do the minimum amount of work necessary to get to the next yield.

But we can use that.

We can make, we can turn that to our advantage.

And that allows us to express solutions in different ways.

Which is what I care about.

I care about you being able to express yourself in code.

Now, if you also care about being able to express yourself in code, I have a book.

Which is this one, and you can find it and order it, at that URL there.

Now, if you want to ask me questions, I am on Mastodon at JR Sinclair at IndieWeb.

Social.

I am JR Sinclair at most other places.

And with that, I will open up to some questions.

WHY WOULD ANYONE NEED JAVASCRIPT GENERATORS?

WHY WOULD ANYONE NEED JAVASCRIPT GENERATOR FUNCTIONS?

WHY WOULD ANYONE CARE ABOUT GENERATOR FUNCTIONS?

1. What is a generator?

2. Efficient code

3. Impossible code

WHAT IS A GENERATOR FUNCTION?

function* myGeneratorFunction() {
  // We create some data
  const claim01 = 'Efficient code';
  console.log('Created some data. Yielding', claim01);
  yield claim01;
  
  // And another bit of data
  const claim02 = 'Impossible code';
  console.log('Created some data. Yielding', claim02);
  yield claim02;
  
  // The line below might not do what you think it does.
  return 42;
}
function* myGeneratorFunction() {
  // We create some data
  const claim01 = 'Efficient code';
  console.log('Created some data. Yielding', claim01);
  yield claim01;

  // And another bit of data
  const claim02 = 'Impossible code';
  console.log('Created some data. Yielding', claim02);
  yield claim02;

  // The line below might not do what you think it does.
  return 42;
}

WHAT HAPPENS IF WE RUN THIS FUNCTION?

let generator = myGeneratorFunction();
James runs the code in a browser console and describes the results

THE GENERATOR, IT DOES NOTHING

function runGenerator(generator) {
    let result;
    do {
        result = generator.next();
        console.log(result);
    } while (!result.done);
}
Screenshot of a JavaScript code editor with a function "runGenerator" that calls the "generator.next()" method in a loop. On the right, it shows the output of a generator function running in a console with debugging information.
function runGenerator(generator) {
  let result;
  do {
    result = generator.next();
    console.log(result);
  } while (!result.done);
}
James runs the code and describes the results

ITERABLE

The slide includes a screenshot of a code editor on the right side, showing JavaScript code with syntax highlighting and console output in what appears to be a browser's developer tools.
function* places() { 
    yield 'World'; 
    yield 'Web Directions'; 
    yield 'Sydney'; 
}

for (let place of places()) { 
    console.log(`Hello ${place}`); 
}
// We can spread the values of a generator
[...places()];
Console displaying JavaScript code and output with various text values like "Hello World", "Hello Web Directions", and "Hello Sydney".

SO WHAT?

A browser's developer console displaying JavaScript code and output.

LIKE ARRAYS, BUT MORE AWKWARD

EFFICIENT CODE

  1. Select a single biscuit.
  2. Bite a small chunk from one corner, 2-5 mm from the apex.
  3. Repeat the bite on the diagonally opposite corner.
  4. Insert one of the bitten corners into a hot beverage. (Milo is traditional, but coffee, tea, or hot chocolate is also acceptable).
  5. Place your lips over the opposite corner, and draw liquid through the biscuit as if it were a straw.
  6. As soon as liquid enters your mouth, immediately consume the entire biscuit. It's important to do this quickly before it loses its structural integrity.
  7. Repeat until there are no more Tim Tams, or you feel physically ill.
const bittenBiscuit = biteArbitraryCorner(biscuit);
const doubleBitten = biteOppositeCorner(bittenBiscuit);
const biscuitInBeverage = insertIntoBeverage(doubleBitten);
const unstableBiscuit = drawLiquid(biscuitInBeverage);
const deliciousness = insertIntoMouth(unstableBiscuit);
An animation of the algorithm.

BUT WHAT IF WE HAVE A WHOLE PACKET OF BISCUITS?


// A naive approach
const MAX_BISCUIT_CONSUMPTION = 5;
biscuits
    .map(biteArbitraryCorner)
    .map(biteOppositeCorner)
    .map(insertIntoBeverage)
    .map(drawLiquid)
    .map(insertIntoMouth)
    .slice(0, MAX_BISCUIT_CONSUMPTION);
An animated version of the agorithm

A naive approach

const MAX_BISCUIT_CONSUMPTION = 5;
biscuits
  .map(biteArbitraryCorner)
  .map(biteOppositeCorner)
  .map(insertIntoBeverage)
  .map(drawLiquid)
  .map(insertIntoMouth)
  .slice(0, MAX_BISCUIT_CONSUMPTION);

HOW DO WE FIX IT?

const map = (transform) => function*(iterable) {
    for (let item of iterable) {
        yield transform(item);
    }
}
const take = (maxResults) => function*(iterable) {
    if (maxResults === 0) return;
    let count = 0;
    for (let item of iterable) {
        yield item;
        count += 1;
        if (count >= maxResults) return;
    }
};
James executes the code in the console and describes the results.
const pipe = (startValue, ...functions) => functions.reduce(
    (val, f) => f(val),
    startValue
);
James executes the code in a console and describes the results.
const sayHello = (x) => `Hello ${x}`;
const legends = (y) => `${y}, you legends!`;
pipe(`Web Directions`, sayHello, legends);
James executes the code in a console and describes the results.
pipe(biscuits,
    map(biteArbitraryCorner),
    map(biteOppositeCorner),
    map(insertIntoBeverage),
    map(drawLiquid),
    map(insertIntoMouth),
    take(MAX_BISCUIT_CONSUMPTION)
);
Animation of the algorithm executing

WHY DOES THAT WORK?

IMPOSSIBLE CODE

function* naturalNumbers() {
    let i = 0;
    while (true) {
        yield i;
        i += 1;
    }
}
James executes the code in a console and describes the results.

WHAT IF WE WERE TO FILTER THAT?

James executes the code in a console and describes the results.
const filter = (shouldKeep) => function* (iterable) {
  for (let item of iterable) {
    if (shouldKeep(item)) yield item;
  }
}
James executes the code in a console and describes the results.
const isEven = x => x % 2 === 0;

const evens = filter(isEven)(naturalNumbers());
James executes the code in a console and describes the results.

const evens = map(x => x * 2)(naturalNumbers());
    
James executes the code in a console and describes the results.

WHAT COULD WE BUILD WITH THAT?

  • Fibonacci sequence
  • Triangular numbers
  • Prime numbers
James executes code in a console and describes the results.

BORING

POINTLESS INTERVIEW QUESTION

EIGHT QUEENS

Given an 8x8 chess board, find a way to place eight queens on the board safely. That is, place them such that no queen can attack any other queen.

Conceptual illustration of a chessboard overlaid with red circular pieces and a single white queen in the center

4,426,165,368

A chessboard has a queen in the top-left corner and red circles filling the leftmost column of the board.
INTERNATIONAL CONVENTION CENTRE
A chessboard with all queens on the top row and zeros on all squares of the bottom row.
A chessboard setup with eight queens and counters below showing 00000017.

4,426,165,368 → 16,777,216

HOW DO WE CONVERT AN INTEGER TO SOMETHING WE CAN TEST?


const natToArray = (n) => (x) => {
   const radix = Math.max(n, 2);
   return x
     .toString(radix)
     .padStart(n, ‘0’)
     .split(‘’)
     .map((i) => parseInt(i, radix));
};
    
const natToArray = (n) => (x) => {
    const radix = Math.max(n, 2);
    return x
        .toString(radix)
        .padStart(n, '0')
        .split('')
        .map((i) => parseInt(i, radix));
};
James executes the code in a console and describes the results.

HOW DO WE CHECK FOR A VALID SOLUTION?

James executes the code in a console and describes the results.
  1. Check for queens in the same row
  2. Check for queens on the same diagonal
  3. (We already ruled out queens in the same column)
James executes the code in a console and describes the results.
const noRepeats = (solution) => new Set(solution).size === solution.length;
James executes the code in a console and describes the results.
const noRepeats = (solution) => new Set(solution).size === solution.length;
James executes the code in a console and describes the results.
const noRepeats = (solution) => new Set(solution).size === solution.length;
James executes the code in a console and describes the results.

WHAT ABOUT THE SAME DIAGONAL?

James executes the code in a console and describes the results.

// Check diagonals for single queen
// Note that y1, x1 and solution match parameters of the .some() callback
const attacksDiagonal = (y1, x1, solution) => 
	solution.some(({y2, x2}) => 
		Math.abs(x1 - x2) == Math.abs(y1 - y2) && x1 !== x2 && y1 !== y2);
James executes the code in a console and describes the results.
const isValidSolution = (solution) =>
    noRepeats(solution) && !solution.some(attacksDiagonal);
    
James executes the code in a console and describes the results.

1. An array representing a base-8 number is difficult to read; and

2. We only want one solution.

James executes the code in a console and describes the results.
// The x-coordinate is just the index in the array.
const solutionToPoints = (solution) => solution.map((y, x) => [x, y]);
James executes the code in a console and describes the results.

An iterator is something that has a .next() method

// like, say, a generator object.

const head = (iterator) => iterator.next().value;
James executes the code in a console and describes the results.

PUTTING IT ALL TOGETHER...

James executes the code in a console and describes the results.
const queens = (n) =>
pipe(
    naturalNumbers(),
    map(natToArray(n)),
    filter(isValidSolution),
    map(solutionToPoints),
    head,
);
James executes the code in a console and describes the results.
const queens = (n) => pipe(
    naturalNumbers(),
    map(natToArray(n)),
    filter(isValidSolution),
    map(solutionToPoints),
    head,
);
James executes the code in a console and describes the results.
Two images are displayed side by side. On the left is a chessboard with 8 queens placed in different positions without threatening each other. The right side shows a screenshot of a coding console output with JavaScript code that generates positions for 8 queens to be safely placed on a chessboard, and an array output demonstrating the positions in the console.

BUT, THAT’S JUST TWO SILLY EXAMPLES. WHAT’S THE POINT?

A Confession

ALTERNATIVE WAYS TO EXPRESS SOLUTIONS

GENERATORS CAN DO MORE THAN JUST BE LAZY

THINGS TO REMEMBER

  • Generators are lazy
  • Laziness changes how data is processed
  • That allows us to express solutions in different ways

CHANGE YOUR PERSPECTIVE

https://jrsinclair.com/skeptics-guide

@jrsinclair@indieweb.social | @jrsinclair
Cover of the book "A Skeptic's Guide to Functional Programming with JavaScript" by James Sinclair, featuring decorative patterns and text: "How to level up your code without alienating your team".