!= Null Can't Hurt You Anymore - Sessions by Pusher

Null Can't Hurt You Anymore

Stefano Vozza speaking at London Node User Group in November, 2016
18Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

This talk follows on from Andreas Møller's session and seeks to build on those foundations with larger abstractions that can help developers to avoid common pitfalls when writing JavaScript, such as null references (Maybes), uncaught exceptions (Eithers) and asynchronous control flow (Futures).


Transcript


So, my name's Stefano, and I'm going to be doing the second talk, which Andreas's talk kind of talked about functions in SMALL and how you sort of plug them together. I'm going to go a slight bit more of a higher instruction level, and concepts that are going to help you write more correct programs, rather than some of the little pieces that you plug together. So, what is the problem? Well, one of them is that it is trivial to introduce runtime errors into Javascript. Everyone has been caught by NullPointer errors here in this room, and it's generally the data you trust the most that ends up burning you. Because of course something can't be null until it actually is, and generally in production on Friday. And of course you get these beautiful error messages that we can see here, "Undefined does not function." What does that mean? It normally means about half an hour, an hour going through stack traces and finding the thing that broke was 50 lines away from what you actually thought it was. So, surely we can do better. Maybe. Sorry. So, you may have heard of this type in Java, or Scala perhaps, the option type. And what it does is it sort of acts as a container for the possibility that something may or may not be there. And it basically lets you handle the null case in a principled, and above all, composable way. So it's quite a simple type. If the thing is there, it gets wrapped in a just. If it's not there, you get returned a nothing. You can just see the examples there. It's pretty simple. And most libraries have this toMaybe function. So, you're just parsing something that may or may not be null, and it gets wrapped for you. So, that's great. It's wrapped. But obviously you probably want to do something with that. So, you're going to have to take it out of the container at some point. So, most libraries, again, will have this fromMaybe function. And the interesting thing about this is that you can't actually unwrap the thing in the maybe unless you deal with the fact that it may or may not have worked. So, you have to give it a default value there. So, you can see in the example there, if the thing you're looking for is not there, you'll get "2" back. So, that's great. You've got it wrapped and you don't have to unwrap it. But that's not really very interesting, that's not very useful. You want to do things to the thing that is actually in the container. And how do you do that? Map, which we've already talked about a little bit earlier. So, we all know this from arrays. But arrays are just one thing that can be mapped over. There's lots of them. If you think of an array as a container that can hold like zero or many values, and map is the thing that applies the function that you're giving to the thing in that container. But you can also think of a maybe as a container that can take precisely zero or one items. And we've all seen code like this. I've written it myself. You've made this function here, null safe, without actually putting any logic in to handle the null case. Because you know that an empty array basically is a null app. And as you can see, if you applied the function and the thing is there, the elements in the array get incremented. So, maybe types are exactly the same. If you've got a just, and you map a function over it, the if function yet applies. Otherwise nothing happens. You can see there what happens if you don't have this type, you just get a TypeError again, at run time, and chances are in production. So how do we compose these things together? We might have multiple operations that could fit. So, we want to see how we can chain these together. Okay? So using what we've seen, let's see how we would we go about composing these options. All right, so let's use map. Okay. That's great. We've created this safe property function, and it's succeeded. So let's apply map again. Okay, that's absolutely useless. So, we've basically wrapped this in, and if we want to do this again, we're going to have to do "map map" and then "map map map." And we're just going to get this hideously nested structure. So, what do we do? We use the magic of flatMap, or chain, as it's also known. So, we replaced the maps with chain, and you'll see why it's called flatMap. I think it's actually probably a better name. So, same thing, we found the thing that we want. Great. Now, when we applied map we got this. If we use chain we get this, and what's happened is something that was on two levels has been flattened down onto one. And you can just do as many chains as you want, and it will always stay the same type and it will always have the same amount of levels. Now, what's the point of all this? So, the main thing is that it helps you separate null safety from core logic. So, this is kind of like a pseudo imperative piece of code. The main thing that it's meant to do is just go through a list, find an object, and if it doesn't find it, it'll return "10," okay? And...there's actually a bug in there. So, I'll come back to that in a second. So what we want to do,"Code the Happy Path," instead of having to worry about things being null all the time. A couple of convenience functions there, findById, which returns a maybe if it doesn't find it, and safePath, which basically just lets us, instead of just like doing prop, prop, prop, you just give it a list of keys, and it gives you back the object or it gives you back a nothing if it's not there. As you can see here, you just compose your object, and right at the end, you say, "If this didn't work, give it back to me." All right? Now, if you look back here. That's fine. We've seen this really defensive code, I've seen this all over the place before. A, it doesn't propose, but B, look. If I don't actually find that object, "num" is always going to be undefined. So, like that was just a trivial example that I put together. But if you are able to do it, like, in something as simple as this, what hope have you got on a larger code base. So, that's maybes. We'll talk about a couple of other types now. One of them is the either type. It's kind of similar to maybe. It encapsulates failure, but it actually gives us some information about the failure. Because if you get a nothing back all you know is that it didn't work or it wasn't there. So, this is good for having functions that throw exceptions, to make them pure. So, in other words, there are type signatures in line. Like, "I could give you a number or I could do something absolutely mental and just crash your program." This, you always get back something. Similar to toMaybe, this is how you would actually make a function that throws an exception safe. You just parse it in the function, you apply it, and then you wrap it in the appropriate type. Okay? Here, JSON.parse, you can obviously throw an exception, and this is just the application. Again, composition, very similar to maybes. It's the exact same API. Here, I've just put a date function here to show that it's not just exceptions. You could make this more granular if you wanted, you could check to see, like, if someone's parsed in 31 days for February or something like that and get back an appropriate error, but not crash the program. And again, it's very simple here, you encase JSON.parse, then you've got a maybe, or you've got an either, and you map over it. And if you notice I'm kind of being massively hypocritical here, and using an unsafe version of prop. That's just because...there are ways, basic compose, maybes, and eithers, but I just don't have time to go into that basically. And again, you use chain, because you don't want to get, like, a left inside a right, or a right inside a right. So, that's kind of skimming over that bit. The last one that I'm going to talk about is a future. So again, it kind of encodes failure, but what it also does is it has an element of time in it. You basically parse a future, a success callback, and a failure callback. The failure call back has to be provided. So, it, basically, just allows you to compose asynchronous actions. Futures are lazy. So, actually, they have a fork method, which actually tells it to do what you want it to do. And this allows you to isolate program side effects, because you decide when they get affected. The thing about future as well is that the fork can be called multiple times. So, it will just reevaluate the computation. So, obviously you're thinking this is just basically a promise, kind of is. There's some subtle differences. So, what a promise then is map, chain, and fork all rolled into one. They're eagerly evaluated. So, when you parse a function to them it starts getting evaluated. So, you can't control the side effects. They're also resolved once, which is why in Java they're called completable futures. So, they basically keep a state. Whereas, as I said, with futures you can just reevaluate them whenever you want. Also error handling is significantly different, everything's wrapped in a tricatch, so errors can get swallowed. You don't actually have to parse the error callback if you don't want to, so it's easy to forget. Whereas, if you don't, with a future, most programs will actually just crash, or most versions will actually just crash. So, how do we use one of these? Again, if you know about promises this looks incredibly similar. Just reading a file, or doing an http request. Make sure you are parsing in your error and your resolve callback. That's great. Say, if you just want to compose these. Again, if you know how to use a maybe and you know how to use an either, you can use a future. Here, we're just reading a file. We're parsing it again in a very unsafe way. And then we're trying to get a URL, and we're going to make an http request. But if you see, we're not actually making the request. We're just describing the computation. We're not actually doing it. It's only until we call this fork method here that we actually initiate the side effect. So, that's pretty much it. I guess the summary for me, is that if you know how to use one of these types you can use them all. They have little subtle differences and that's just sort of around what the container does. You concentrate your logic in one place. So, you don't have your code littered with those really defensive if statements that we've seen. And the only reason that they're there is because something broke. Almost 100% sure of that. It also allows you to push the unsafe parts of your program to the edge. When you get something, you wrap it in this type. You say, "Fine, you're dangerous." And then right at the end... you program as if everything's perfectly fine. And right at the end, that's when you deal with it. And again, you're forced to deal with it, because these types won't work if you don't. And that's pretty much it.