The Miracle of Generators
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 133 | |
Author | ||
License | CC Attribution - NonCommercial - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/48840 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Electric generatorSoftware developerStandard deviationJSONXMLUMLComputer animation
00:42
Software developerElectric generatorRight angleTaylor seriesMultiplication signBitObject (grammar)Functional (mathematics)Data storage deviceGraph coloringMereologyQuicksortSymbol tableString (computer science)System callFormal languageAdditionScripting languageAreaArray data structureSoftware testingComputer programmingInterface (computing)DigitizingFinite-state machineRule of inferenceElectronic mailing listPoint (geometry)ExpressionBeer steinResultantElectric generatorIterationObject-oriented programmingConstructor (object-oriented programming)God2 (number)Computer animation
07:14
Array data structureSoftware developerInfinityLogarithmVideo game consoleFunction (mathematics)Menu (computing)SequenceStreaming mediaPoint (geometry)IterationFunctional (mathematics)InfinityQuicksortLevel (video gaming)Image resolutionObject (grammar)Formal languageGraphical user interfaceCASE <Informatik>Web browserBitCategory of beingBoilerplate (text)Normal (geometry)Electronic mailing listLibrary (computing)Array data structureLoop (music)ExpressionPerformance appraisalPower (physics)Data structureNetwork topologyFamilyInstance (computer science)CodeMultiplication signNumberSineBit rateResultantString (computer science)Algebraic closureComputer animation
13:16
Software developerFunction (mathematics)InfinityMultiplication signWebsiteFunctional (mathematics)IterationStatement (computer science)Message passingParameter (computer programming)Electric generatorTopological algebraArray data structurePoint (geometry)Generating functionVariable (mathematics)Level (video gaming)Interface (computing)Formal languageQuicksortInfinityLoop (music)Library (computing)ExpressionBitStreaming mediaResultantString (computer science)Boilerplate (text)Computer scienceNumberLengthSign (mathematics)Right angleMetropolitan area networkGroup actionWeightLabour Party (Malta)Semiconductor memoryMetreOcean currentEvent horizonMoment (mathematics)Observational studySystem callWave packetScripting languageNetwork topologyComputer virusComputer animation
21:50
Software developerMenu (computing)Limit (category theory)Execution unitInfinityCAN busLogarithmFunction (mathematics)Parameter (computer programming)Web browserLoginStandard deviationLengthVideo game consoleUniform resource locatorFunctional (mathematics)BitDefault (computer science)Proper mapCodeMashup <Internet>IterationMultiplication signWordExecution unitQuicksortOperator (mathematics)NumberObject (grammar)Resolvent formalismPoint (geometry)Hand fanMoment (mathematics)2 (number)ResultantGenerating functionInstance (computer science)Dependent and independent variablesRecursionCheat <Computerspiel>CASE <Informatik>MereologyProgrammable read-only memoryTopological algebraAxiom of choiceMetropolitan area networkData structureGroup actionSystem callMaizeMedical imagingKey (cryptography)InfinityService (economics)FreewareReal numberVideo GenieComputer animation
31:29
Software developerCAN busFunction (mathematics)Lie groupPiScripting languageType theoryCrash (computing)Key (cryptography)Java appletParameter (computer programming)Object (grammar)CodeFormal languageFunctional (mathematics)Generating functionDependent and independent variablesUniform resource locatorChainingError messageResultantVariable (mathematics)Category of beingIterationTopological algebraCASE <Informatik>Statement (computer science)Arithmetic progressionOperator (mathematics)QuicksortException handlingPoint (geometry)Run time (program lifecycle phase)Right angleStack (abstract data type)Single-precision floating-point formatPhysical systemSet (mathematics)Heat transferInternetworkingMetreBit rateCausalityPresentation of a groupLetterpress printingDifferent (Kate Ryan album)Internet service providerResolvent formalismComa BerenicesMultiplication signElectric generatorWeight8 (number)Hand fanEvent horizonComputer animation
41:08
IRIS-TSoftware developerFunction (mathematics)Chemical equationPiCAN busInterface (computing)Interface (computing)Execution unitChainingOperations researchChainConvex hullEmailComputer fileSound effectSynchronizationFunctional (mathematics)String (computer science)Type theoryPointer (computer programming)Topological algebraOperator (mathematics)Generating functionQuicksortPerfect groupObject (grammar)Point (geometry)Constructor (object-oriented programming)Computer programmingComputer fileCASE <Informatik>Group actionImage resolutionCategory of beingMechanism designExecution unitSign (mathematics)Software testingChainingError messageState of matterEqualiser (mathematics)Parameter (computer programming)Resolvent formalismInterface (computing)PiRevision controlResultantSound effectKey (cryptography)Similarity (geometry)CodeBitMultiplication signDirected graphPositional notationVariable (mathematics)Proper mapEquivalence relationRight angleMetropolitan area networkSystem callHand fanComa BerenicesObservational studyBargaining problemForcing (mathematics)Process (computing)Shooting methodMassComputer animation
50:47
Software developerMaxima and minimaMonad (category theory)Interface (computing)Execution unitComputer filePhysical lawLibrary (computing)Term (mathematics)Functional (mathematics)Image resolutionWritingKeyboard shortcutMonad (category theory)Topological algebraAssociative propertyCodePositional notationQuicksortPointer (computer programming)Programmer (hardware)Library (computing)Revision controlScripting languageCASE <Informatik>System callInsertion lossMoment (mathematics)ImplementationLatin squareRight angleValue-added networkIdentity managementData miningFood energyMathematicsMetropolitan area networkComputer animation
54:34
Electric generatorSoftware developerFibonacci numberWeb browserSlide ruleGraphical user interfaceText editorRiflingBitRight angleGoodness of fitComputer animation
55:17
Software developerComputer animation
Transcript: English(auto-generated)
00:06
Well, so this talk is going to be about some interesting new features in ES6. ES2015, we're supposed to call it now. The new official JavaScript standard that I've been playing around with, and I've gotten really excited about the things you can do with them.
00:25
Like, I picked up this gift for you to illustrate about how excited I am about these things. So this is Sid. Sid is a beagle from Austin, Texas, and he has this thing where he likes to just hold food in his mouth
00:42
and sit there and look very pleased with himself, wagging his tail and salivating. You can actually see some salivation right at the end before it leaves, just out the right side of his mouth. There you go. Did you see it? One more time. Bit of saliva.
01:00
There you go. Sid is the most excited creature in the world in that picture. And that echoes the way I feel about ES6 generators, which I'm going to show you today. So before I can get to generators, though, I need to show you another feature in ES6 called iterators.
01:26
And if you're doing any sort of object-oriented programming, and I suppose if you're not doing JavaScript here, you're probably doing C-sharp, so this is not going to be news to you, probably, but so there's this thing that's been standardized now in ES6 called iterators.
01:45
So, if you have an array in JavaScript, an array of ponies, about six of them, there is now an iterable interface, and you've got a new method called values on the array,
02:07
which gets you an object called an iterator. And we can explore that a bit. So I'm assigning it to the variable i, so I can call it.
02:20
An iterator is just something that has a next method, and the next method will simply iterate over the values in the iterable. So we get Applejack, Fluttershy, Pinkie Pie, Rainbow Dash, Rarity, Twilight Sparkle, and we're done. That's enough pennies. So you get the idea.
02:41
Whenever you call next, you get an object back describing the value that's coming out of this iteration, and you've got the done flag, whether or not you're done iterating. And at the end, we don't get a value, but we get done true, which means no more objects in this iteration.
03:02
So that's fairly straightforward, but it's been iterated a bit in the language, like you have a for construct now that will take an iterator or iterable and let you iterate over it. So essentially, for i of ponies, I can go console.log.
03:24
Oh my god. Of, of. Oh, and the i, of course. It's only in the morning. There we go. So now we have a language construct that actually uses iterators, and already we're off to a decent start,
03:46
because now we don't have to do the weird three-part C-style for-animal to iterate over things. To iterate over arrays, at least. So already we've improved JavaScript quite a bit. Not that it took much doing, but yay, JavaScript's getting better.
04:02
Oh, the rules of live coding, incidentally, which you just discovered. If you see me making a mistake, you need to tell me immediately. That, obviously, is me testing you. So I might put in some mistakes in here.
04:24
Obviously, I'm not making them. You have to spot them. Then I'll know you're very smart people. Is that a plausible excuse? Anyway, help me pay attention if I mess up. So that was iterating over arrays.
04:41
Not very exciting. But the interesting bit is because the iterable is just sort of an interface. You can write completely custom iterators and iterables. So I created an object here that has a symbol.iterator method.
05:00
Symbol, incidentally, is another new thing in ES6. It's more or less like symbols in Lisp. And they can be used as method names in addition to strings in ES6. And for whatever reason, symbol.iterator is the method name
05:23
that has to be there on an iterable to make it iterable. So symbol.iterator is a function which will return an iterator object. And the iterator object has the next method, as we've seen. And here I've just sort of created a very simple state machine
05:41
that starts counting at zero. It counts up every time you call it. And the first time, it returns Pinkapie. The second time, it returns Rainbow Dash. And I couldn't be arsed to put in more ponies than that for this super example. So let's run it.
06:03
So first, to get the iterator out of this one, I need to actually call symbol.iterable iterator. Uh-oh. You didn't spot that one, did you? So I get an iterator. That's essentially the object right here that I'm returning.
06:26
And I can call that the first time C is going to go to one, and I'm going to get Pinkapie. C is going to go to two, I'm going to get Rainbow Dash. Then it's going to go to three, which isn't handled. And it returns to untrue. And the cool thing about this is it integrates neatly
06:44
with all the language constructs. Just like I can go, at least I believe I can, of ponies to this one again. Except now it's not an array, it's just something that has an iterator interface.
07:00
I can also call array.from, which is a new thing as well, which takes any iterable and turns it into an array. So here's a list of two ponies. And at this point, I'm going to introduce you to a little hack that I've put into my REPL, just to make things easier.
07:23
So whenever I start an expression with an ampersand, it means please resolve this for me. In particular, if I give it an iterable or an iterator, it just resolves it to, it iterates over it, essentially.
07:43
Later, I'm going to use it to resolve promises as well. But for now, just think of the ampersand as please resolve this object into what it represents. Keep that in mind. Now let's go crazy.
08:00
So I've written another customer tracer here called infinity. As you can see, all it does is it starts counting at zero and does that forever and never ends. So literally, I can get an iterator from this.
08:24
And I can iterate over it for you. Let i of infinity, yeah? This is going to end well.
08:42
Actually, no, I'm not going to run that. The thing is, it's going to crash my browser. And as I'm running on Chrome OS, which is literally the OS, it's the browser. That will be a bit unfortunate. So I'm not going to do this. I'm just going to call this a bit
09:01
so you can see that it works. 4, 5, 6, 7, 8, and so on. And of course, I can do the resolution thing as well. That one only gets the first 10 values out of a iterator. So things don't go completely crazy. That's cool.
09:22
You might notice, though, that we have the ability now to make infinite streams, streams that don't end. And more importantly, the streams are evaluated lazily. Whenever you call next, it computes the next value. So you can have infinite streams
09:40
without going into an infinite loop. As long as you're careful. Which leads into some interesting properties. You can write a function, and let's call it take. We can build up a library of useful functions you can apply to iterators, like the take function.
10:03
Because infinity, of course, we need to be a bit careful about how we use it. So the take function essentially returns it takes a number, and it takes an iterable, and it returns a new iterable with slightly different properties.
10:22
In the case of take, it only takes the first and items out of the iterator. So this literally gets us the first five items out of infinity. This is really exciting.
10:40
Of course, we can use that as any iterator. As you can see, it's an iterator. Slightly more interesting, though, we could also add something like a map function.
11:02
Which, let's start off with take five out of infinity, just to see that it's still there. And then we can map over it. So map takes a function to be applied to every object that comes out of the underlying iterator.
11:23
So i is i plus five. For instance, it's actually given us a sequence that starts at five and counts up to nine. And of course, we're all basing this on the infinity sequence.
11:41
So we can see that this is all computed completely lazily. And that can be, if you know any Haskell at all, you will be nodding along at this point. That can be a very powerful language feature, being able to do lazy evaluation.
12:02
In most languages that support it, it won't be owned by default, like in Haskell. Like, say, Clojure uses almost this specific feature very frequently throughout the language design. Essentially, Clojure will have normal data structures,
12:21
like lists and arrays. But whenever you apply one of the sequence functions on it, like map, you will get a lazy sequence back. Which can be extremely useful, especially when you have infinite sequences, as you're going to say, I'm sure will terminate.
12:42
Anyway, though, look at this. Look at this. This is sort of hideous. The map function is a lot of boilerplate. Likewise, the take function. Literally, we've got a function that returns an object which has another function,
13:01
which returns an object which has yet another function. And this is not nice. This is not a good way of writing code, really. I wish there were a way we could do this better. I'm going to pause before I dive into how to do that better
13:21
with a shot of Sid here with a donut in his mouth. I don't think it's elevating this time. No. It looks like he's containing himself. Still looks quite pleased. Let's move on. So how can I rewrite infinity? Let's check that works.
13:45
Into something that's a little bit nicer, a little bit more readable. This is where generators come in. So I'm going to get rid of that. And I'm going to write my own function. Infinity is function star.
14:00
This is a generator function. The star denotes that this is not just an ordinary function. This is a generator function. It can take arguments. This one isn't going to. And the thing with a generator function is it doesn't run immediately.
14:22
Instead, it returns an iterator. And let's just write the function out. So we're going to start at zero for our infinity stream. And essentially, y is true. Infinite loop. We're going to yield.
14:41
C++. That's not the language. That's just incrementing the c variable. So the yield keyword is new and specific to generator functions. That means essentially when I call the iterator that this function will return, it will start running the function until it hits a yield statement.
15:01
And then it will return the expression that you passed to the yield statement as the iterator value. Which means that... Actually, let's just try calling it. Calling the infinity function and I get an iterator.
15:20
Actually, a generator object. And this function hasn't started running yet. Not until I call i.next. At that point, it's going to initialize the variable. It's going to enter the infinite loop. It's going to get the value of c.
15:44
It's not going to increment it before I get the value out. One of the peculiarities of C-style language is that the ++ prefix will get the value, then increment it. So the zero that we get out of this expression
16:03
is going to hit the yield statement. And at that point, the function will simply pause and return the value out of the iterator. That's value zero and done falls. Now, when I call it the next time, I get one.
16:24
It's restarted the function. So it hits the infinite loop, it gets the value and it increments again. And so on. So this is essentially just a much neater way of writing iterators.
16:41
It also happens to be a coroutine, which is a rather useful computer science concept. I'm not going to go into detail about coroutines, but they can be really, really powerful. This is essentially a function that we can pause
17:02
and resume whenever we like. And the iterator is the interface through which we do that. That's quite neat. So I have rewritten the map and take functions as iterators.
17:26
And you'll notice they are about one-third the length they used to be. There's no sign of the boilerplate. Let's look at the map function. Takes the same arguments. This time it goes into a loop over the iterator
17:45
that you passed to it. Essentially, we get values out of it until it tells us we're done. And we yield the results of mapping the function over each of these values. So they work in exactly the same way.
18:05
We can go take five out of infinity and nothing surprising there. We can map i plus five over infinity.
18:21
So let's go crazy. Let's map like one million. And that seems to work too. So we have recreated this tiny little stream library using generators. And it's just a little neater, isn't it?
18:44
So that's all well and good. But so far, we haven't really done anything particularly exciting, anything that you can't easily do in most languages, just with slightly less boilerplate, perhaps.
19:01
So iterators are great, but their usefulness only goes so far. This is where it gets interesting, though. Because it turns out generator functions aren't just one way. They are just for writing functions
19:20
from out of which things, values, come. You can also pass values into them, like this very, very stupid example here, the five up function. And essentially, it starts with initializing this variable to zero, goes into an infinite loop, and yields the value.
19:43
Then notice this bit. It assigns the result of the yield to the variable. Then it increments it by five and goes into the loop again. The thing is, let's get an iterator out of this one.
20:06
The thing is, of course, the first time we call it, there's going to be no variable assignment, because we have to get to the yield first. And this is just the way generators are.
20:20
If you're going to put things into them, you first have to call them once, just without a value, or at any rate, the value has to be ignored. So we're sort of priming it this time, and we get the expected zero out. Now we can start to get to pass things into it. So if I pass five here, it will restart the function
20:43
at the point where it is waiting for the value to assign to the variable. So C is going to be five. C is going to get incremented to ten, and we're going to yield that. So ten, whoa.
21:01
Pass ten in, you get 15. Pass minus five in, you get zero. Pass one million, you get a million and five. Pass string five, you get 55 because, yeah, JavaScript. Pass not a number, you get null, that's interesting. I was actually expecting not a number.
21:22
Pass empty array, what are we going to get? Do you know JavaScript trivia? I honestly have no idea. Oh, string empty array five, that's interesting. You've got to love JavaScript. Anyway, a bit of a useless example of putting things into generators there,
21:43
but I'm sure you're all thinking exactly the same thing I thought when I saw this the first time. And actually before we get to thinking, let's pause for a moment and admire Sid holding bacon in his mouth.
22:02
I don't know how his eyes just go, sort of squint a bit. Anyway, what if instead of just putting numbers in and getting incremented numbers out, which isn't very useful, what if you put in promises?
22:25
So promises, if you're not aware, that's another new standard that's been put into ES6, essentially a way to do a single operation that's a little less awfully than nodes traditional callback.
22:43
A promise is an object that will at some point resolve with a value, and you can call the then method on it to attach a handler for when that happens. So a promise is something that will get a value in the future,
23:00
called then on the promise with a function to receive that value when it happens. And promises are nice for writing async code. It's cleaner than the callback that we used from node, but you still have to deal with callbacks, essentially.
23:23
It's just a bit, you sort of flattened the pyramid a bit, that's all. However, these turn out to blend rather well with generators.
23:41
So first things first, I have a unit function here. It simply takes a value and it returns a promise that is already resolved with that particular value. So unit of five returns us a promise object. You'll see it has a value of five already.
24:01
So I could go unit five then console.log, and it will log five right at the end there because it's already resolved with a value of five. That's just to get us started with promises.
24:22
Now what if we have a generator function that console.logs a yield on unit OMG, just to use a couple of everyday words,
24:42
like OMG, WTF, BBQ, just standard words that you would use in every sentence. So if we get an iterator out of that,
25:04
and we call either next or not, what comes out? A promise. And you'll note that the promise is already resolved with the value OMG, as we expect from the unit OMG down there.
25:24
So now we pass, say, the value OMG back. That's going to go to the console.log, and it logs OMG. We've essentially sort of manually resolved the promise here. We don't have to pass what comes out.
25:42
Look at this one. The next promise has WTF in it, which is a bit rude, so let's go LOL instead. And it logs LOL. And it gives us a new promise to resolve. It's pre-resolved to BBQ. We don't care. We're going to go with negative infinity,
26:03
which is a bit difficult. It doesn't really matter. Log is negative infinity, and it signals that we're done. So we've gone through all three console.logs. We could, however, write a function to do this properly. Let's call it run.
26:22
It takes an iterator, and I'm going to do recursion here because that's the only neat way of doing it, so I'm going to take the value that we're going to resolve as a second argument and default it to null for the first time we call it. This is another cool new feature in ESX, incidentally,
26:42
the default value for an argument. So if I call this function with just an iterator, then VAL will be pre-declared to be null. So I get a next out of it by calling it a .next with VAL,
27:05
which the first time is going to be null, and as I mentioned, the first time we just have to prime the iterator, so that's fine. The second time, however, unless we are done iterating,
27:25
unless we're done iterating, we get the value that the iterator returned to us, and that's a promise now,
27:41
or it's supposed to be. So we call then on it to get the value out of the promise, and that takes a function which will get the result, and we recurse. We pass the iterator and the result value back to run,
28:08
which will keep going until we hit the done, of course. So if we now pass, whoops, sorry, scenario function.
28:25
So if we pass promises to run, it will resolve the promises for us asynchronously,
28:42
as it must be, and it will simply run this function as if the promises were synchronous code. Of course, these promises aren't actually asynchronous, they're pre-resolved, they're sort of cheating, but notice how we write this code.
29:03
We essentially write it exactly as if it were a blocking operation, and we just put yield in front of everything we need to resolve from a promise, and using the little run function, that goes seamlessly. So that's getting pretty interesting.
29:20
Now let's see a real example. So here's another new thing. This isn't an ES6 thing, this is a browser standard thing. The new and improved XML HTTP request, essentially. XML HTTP request, great name for it, incidentally.
29:44
It's simply a way of doing asynchronous HTTP calls from the browser. And in the olden days, when this was named, it seemed obvious that what else would you need to fetch than XML, hence XML HTTP request.
30:04
XHR for short, and fortunately nowadays you don't know how to use it. It's a bit of a horror to use. Nowadays we simply have the fetch function. The fetch function takes a URL argument and it returns us a promise,
30:20
and the promise resolves to a response object, and the response object has methods that you can call on it, like we need to resolve it, and then we can call result.text, for instance.
30:44
So result.text resolves to the text of the document that you fetched, essentially. So this gets us the first paragraph of the epic work of fan fiction, Fallout Equestria, which is a mashup
31:03
of My Little Pony and the Fallout franchise. And if you know either of them, you know that's got to be amazing, and indeed it is. I highly recommend this book. It's about the length of war and peace, famously. It's totally worth it if you like ponies and Fallout.
31:20
But it might be you don't. That's fine. Anyway, I can write a function. I'm going to call it Fallout. It's going to be a generated function, and I'm going to do this thing that I just did
31:41
in the style of the run function. So first I need to get a response object, so I yield on fetch, and then I need to get the text object,
32:01
which is also returning a promise. Response.text. Then finally I can log the result of that. So I have written some code that looks completely like
32:23
your average blocking Java or C sharp code, except for the yield statement. And it's completely asynchronous, except it doesn't look like it, so you don't really have to think about it when you write it. So let's try and run it.
32:43
Run Fallout. Work seamlessly. That's actually quite exciting. We've almost done away completely with callback, haven't we? I'd like to improve on the run function a bit, though, because this function is not very generalizable.
33:05
I could maybe give it an argument and fetch that instead, but this function can do nothing other than console.log the result, which is sort of not necessarily very useful.
33:22
So can we generalize it? Can we actually get these runnable functions to return us a value that we can do whatever we like with? We need to change run a little bit, or extend it at least.
33:41
So you can return things as it happens from generators, and you can also return something from this one. So essentially, I want to extend the run function so that it returns a promise that will eventually result to what we return ultimately from the generator function.
34:04
So I need to put that promise in somewhere, pull it down, and I'm going to use promise.defer, and I'm going to return done.
34:24
And I need to check for when the iterator is done so we can fetch out the value that we return and resolve done with it. Done.promise, sorry. Promise, okay. So if next is not done, then we proceed as normal,
34:43
except we also need to pass done. Else, if we are done, we can resolve that deferred object with a next.value. As it turns out, if you have a return statement
35:03
at the end of a generator function, then when you're done iterating, you get the done true, and you also get a value, a property. So we're going to take advantage of that, and we're going to write as a completely generalized fetch function
35:21
for fetching some text. Let's call it fetchTest. That's a generator function. It takes an argument, and it... Let's just go all out and not use the intermediate variables.
35:43
So we fetch the URL, the URL. We yield on it. Then we get the text of what we yielded, and we yield that. And then ultimately we return it.
36:01
Actually, this looks horrible. Let's go. const response is yield fetch. const text is yield response.text, exactly like before. Then instead of console.logging it, we return text.
36:24
It's a bit clearer. It's exactly the same thing, but you could... If you hate your coworkers, you could definitely write it like this. Exactly the same thing. Huh? Oh, I thought so. Thank you.
36:43
I thought there was something wrong with that. So let's try running it, shall we? Run fetch text, and then we should just be able to pass that, the URL, data.novel.text.
37:00
And it should run that. Go and fetch it. Run it through the fetch text function. You yield a couple of promises, ultimately returning that value which gets resolved in the promise that we get back from run, which then gets resolved by the ampersand.
37:21
So... That was a different thing. That wasn't supposed to happen. I'm sorry. This is just something I've been writing for myself. Actually, this is... This is the Mary Sue fanfic rewritten slightly.
37:41
Um... Fallout the text. I'm sorry about that. There we go. So at least now we know that this was completely generalized. You can fetch anything. Any argument you give it, it will go out and fetch for you. I wonder if we could do like...
38:02
I'm going off script now, mind you. So if this crashes my presentation, then I'm sorry. Oh, right. I'm not actually connected to the Internet, so this is just going to do nothing. Reasonable.
38:21
How about we go localhost? Just to prove the point. Oh! That first donation will document. So that looks legit. So... I believe we have solved callback hell.
38:41
A lot neater than going... Running around with promises and then chains with callback functions which are just cheating, really. We've solved it very neatly. Isn't this completely readable? I think exactly what you'd expect from like a normal language.
39:04
I think we have made some progress today. But there's more. It doesn't need to be an async operation. Anything that has a then method works with the run function and generate it.
39:21
Like I say, take this use case. So I got an object with ponies in it because it's a good thing to put in an object. I got this generated function things which gets Rainbow Dash out of the object, it gets Pinkie Pie out of the object,
39:41
and it returns the fact that Rainbow Dash is friends with Pinkie Pie. So running that, we get Rainbow Dash's friends with Pinkie Pie. Now, what happens if we inquire about Twilight Sparkle? Who is, of course, also friends with Rainbow Dash.
40:05
So we note that there is no Twilight Sparkle key in our object. So, of course, a JavaScript type checker would catch this at compile time, yes? No.
40:20
I wish JavaScript had a type checker. But, of course, the JavaScript runtime is going to catch this at runtime and throw an exception, obviously. I mean, a reasonably sane language, right? Never mind the 55 earlier.
40:41
So, yeah. What happens, of course, is that JavaScript has no problem with you accessing keys that don't exist on an object. It will just give you undefined back. So we know that Rainbow Dash is friends with undefined, which is not what we wanted. We would have preferred to handle this error in some way,
41:03
or at least get the error reported inside of the JavaScript, just assuming that, oh, you asked for something that doesn't exist. You probably want your program to continue as normal with the undefined value instead of what you thought you were going to get. Makes perfect sense in the world of JavaScript.
41:23
So how would we handle this? Here's one rather silly way. I'm going to introduce something vulnerable, which I'm going to call maybe.
41:41
Or maybe it's the constructor function for it. It takes a value, and it returns us a thenable, which simply means something that has a then property, which is the function, which takes a callback function. Let's call it callback.
42:04
And then if the value is neither null nor undefined, this is a bit terrifying because Douglas Crockford explicitly told us that you always use the triple equal or the triple unequal, or he will know and come for you.
42:23
But in this case, the thing with the double equals there is that it doesn't test for exact equality. It tests for the sort of equality it might think you wanted, which is why it's recommended normally not to use it.
42:40
But in the case of double equaling something to null, it means null or undefined will match. So in this case, if our value is neither null nor undefined, then we pass it to the callback function, which I renamed to callback.
43:01
Otherwise, we just return null. We do nothing. Which means that if you pass a null value to the maybe constructor, you get a then, which will never call your callback. And we can extend that to...
43:22
We need... No, let's test it just to make sure that it works. So maybe of five, that returns something that resolves to five. Note, maybe of five, it returns an object, and you can't see the properties on it, but they're there.
43:42
And if we resolve it by calling then, we get five. And we can call them directly. We can chain these with a new maybe, maybe of six. That would get us a chain, which ultimately returns six. And now for the mechanism.
44:02
If we have null and six, we get nothing back. That isn't... The null isn't the resolution. It is the return value of the maybe. The resolution never happens. This function here, i to maybe of six, that's never called because of the null.
44:24
What if we pass a string null? Is that going to resolve? You never know with JavaScript. Actually, yes, string null is not a nullable object. At least, that's something, I guess.
44:41
So, let us create a prop function, which takes a key and an object, and returns the maybe of looking up that key on the object, which means that we can rewrite this function as such.
45:05
Or shall we try it? Prop pi of ponies. Resolve it. And we get Pinkie Pie. That's why nothing happens.
45:21
So we can rewrite. Instead of ponies.dash, we yield on prop dash of ponies. And instead of, oh, not pi, we yield prop pi of ponies,
45:43
and we return the fact that they are indeed friends. Oh, thank you. Good spot. So we can run that.
46:00
Run things. And we get, as previously, Rainbow Dash is friends with Pinkie Pie. Now, changing that to twine, what's going to happen now? Nothing. Nothing at all.
46:20
So, I might have preferred that we actually returned an error in the case that we get a null value out of the prop function. But at least doing nothing is better than doing the wrong thing, right? I think maybe it's a philosophical question.
46:43
But that's at least another use case for the run function, combined with something that looks like a promise, which isn't just an asynchronous operation, an IO operation. So let's summarize. We have an interface for async operations,
47:03
or things that are sort of like them. So this is a TypeScript notation, incidentally, just to put some types in there. So if something has a method called than, which takes a function, which takes an argument A
47:21
and returns an async of type B, and returns another async of type B, which I didn't implement properly in the maybe example, but this is at least how it works with promises, we could extend that with the unit function that I used earlier
47:41
to create promises out of values, promises that are predefined. Essentially, a unit function is a way to get a value into the async operation without needing to perform one, really. So what's that given us? We can chain operations using than.
48:01
That's how promises work. But we can make the than chains go away using yields, combining it with the run function that I showed you, making essentially very, very neat-looking asynchronous code. We can do this with anything, which has a than method.
48:20
It's not just about promises. We can do the maybe trick. We could do that with arrays, actually, for some interesting effects, many other use cases. So imagine a world without callbacks. This is an example of how you can get out a callback hell
48:41
in the most notorious example of callback hell, Node.js. This is a node function, a node program essentially, which reads the file a.text, then the file b.text, then writes the concatenation of those two
49:00
out of the file b.text using the run function and a generator. And there's no sign of callbacks at all whatsoever. And it works exactly like the equivalent rather nasty-looking, I imagine, example of callback hell. This is actually just like in Haskell.
49:25
This is the exact same program in Haskell. It essentially sort of has this generator function do notation, where it reads the file, reads another file, assigns them to variables, writes them back out.
49:41
Haskell is actually all callbacks because it has this property that no side effects are allowed whatsoever, which means that all your IO action has to be async. The only way to perform an IO operation in Haskell is by returning a value to the runtime, saying, could you please, at some point in the future,
50:02
perform this operation for me and call this function that I pass you when you're done with the result of the operation? So there's a surprising similarity there between Haskell and Node.js in how side effects are handled.
50:22
And Haskell's been around for a bit longer than Node. They were actually here 25 years ago. This is actually not Haskell. This is the callback hell version of the program that I showed you earlier in Node. And that was actually, Haskell was in an even worse state than this
50:42
before they found a solution for this problem. But they did. Because it was completely untenable to write code like that, at least for academics, where things have to be neat. So finally, eventually, one man, Philip Woodler,
51:00
came up with a solution to how to go about writing neat code in Haskell without the callback hell. And he called them monads. I know what you're thinking. She's going to launch into a monad tutorial, isn't she?
51:21
Well, no. So this is a monad. This is a monad in Haskell syntax. It's something that has these two functions available to it. The return function and the bind function. This is how you write bind in Haskell. Greater than, greater than, equals, obviously, is pronounced bind. Makes perfect sense.
51:42
This is maybe not something you know how to read. So let's translate it to TypeScript notation. It looks sort of familiar, doesn't it? We've got the unit function. We've got the then function, which takes a callback with the resolution of the monad.
52:04
So, actually, you have seen the IO monad, which we call Promise in JavaScript. You've seen the maybe monad, or at least my simplified version of it, for handling null values.
52:22
It's almost exactly the same in Haskell. And it turns out... So this is monad comprehension in Haskell. This is what they call monad comprehension, the do block. And this is monad comprehension 2.
52:41
This is that little run function with the generator. And you yield, essentially, on monadic values, which, in this case, are promises. So I'm going to suggest that I've just taught you monads.
53:02
Of course, are there any Haskell programmers in the audience? A couple. So I'm sure you're all going, well, actually, that's not the whole story. There's also the three monad laws. Left identity, right identity, associativity. I'll try to express them in JavaScript terms here.
53:26
The mathematical triple equals is even in place. These have to be fulfilled as well, to be allowed to call it a monad. As it turns out, promises do fulfill these. As it turns out, promises, as implemented,
53:42
do slightly more than monads. So there is some controversy about whether you're allowed to call them monads, but they are definitely close enough. So we're going to call them monads. Well, I'm sorry. I've accidentally taught you monads.
54:00
Before I close, I'd like to mention the Co library. If you want to go and try doing what you've just seen in JavaScript, then you might use my little run function, but you might also go and have a look at Co, which does it a little better. It's exactly the same thing in principle.
54:22
It's just a more complete implementation of it. So you should absolutely check that out. If this looks interesting at all. That's all I got, and here's some SID for you. If you want to check out my slides and my raffle,
54:41
it runs fine in any browser that is Chrome, and probably Firefox, and I've not tested it in anything else, but I'm sure it works in Chrome. I've just run it in Chrome. You don't need anything else. You just literally open that URL right there,
55:00
and you will get my slides with the editor and the raffle, and you can play with it. Here's SID with pizza. It's actually gnawing a bit right at the end there, you'll notice. Getting a bit too excited. Looks like some very good pizza. And that's all I got. Thank you so much for listening.