 Siwydfodd, ydych chi'n gwybod am y cyfrifol yn y gafodd y nod? Rwy'n hyn yn ymgyrchu'r cyfrifol mewn cyfrifol, mae'n gwybod. Rwy'n hyn yn ymgyrchu. Felly mae'r cyfrifol yn ymgyrchu. Rwy'n hyn yn ymgyrchu'r cyfrifol, mae'n ei gael y Llywodraeth yn y Llywodraeth Cymru. Mae'r cyfrifol yn gyfgaredd. Rydych yn ymgyrchu'r cyfrifol mewn cyfrifol, O'r hyffordd yn ymweld i'r amddangos, maen nhw'n gweld eu ddweud â'r hyn yn ymgyrch yn ymddangos? Roedd, ddweud, ddweud, ddweud, ddweud, ddweud, ddweud, ddweud, ddweud, ddweud, ddweud! O, ymgyrch yn ddweud. Roedd, yn ymgyrch yn ddweud, yn ddweud i'r ymgyrch i'r hyn. Yn ymgyrch yn y pethau ymlaen, ychydig o'r pethau. Yn ymgyrch o'r pethau, os ymgyrch yn ymgyrch, Onw ydych chi'n fawr ond i'r gaelio fel gyda'i hyfforddiad. Felly, ydych chi i'r cyflenni arfer, yw yn rhani gyda'r gwerthoedd, a nid oedd hyblynedd ag y dyrebu ystauwch yn gweithio. Mae'n wneud gyda hyfforddiad yn yr hyfforddiad a dyna rhai. Mae wrth hyfforddiad bod nid o'r gwerthiau sydd ar hyfforddiad. Ond yn rhani gyda'r gaelio, rwy'n yn ysafwyr, ti'n ddim yn rhani gyda'r gwerthoedd. mae'n bwysig i'r ffordd ac yn ystod yn ôl, ond mae'n bwysig i'r llunio cyfnod. Mae'r ysgol yn bwysig i'r rhop sy'n cael ei wneud i'r helpu i'r rhop ffaith. A byddai'n cael ei gweithio'r rhop sy'n cael ei wneud, ond mae'n bwysig i'r rhop. Felly, mae'r rhop sy'n cael ei wneud, mae'n bwysig i'r rhop a gennaerwch. Felly mae'r llunio'r llunio'r rhop, yn ymwysig i'r sefyllfa o'r llefnod fath, ond mae'n fwynhau i'r gwahaniaeth. Gwyddon ni'n fwy o i'r rhop sy'n ymlaen? O, y ffordd. Fy hoeddi. Felly mae'n rhoi'r gynarych i gyda'r peth ffaith yn llawer o'i llunio'r rhop sydd arall, ac yn ddweud o gweithio'r rhop. Yn y gallu ymddans, mae'n ddweud o'r plannu haf hynny ddjanol, You need to learn the ropes. So that's what this talk is, I'm going to teach you about how to use generics. So hopefully it will open more places on that mountain side while still remaining typesafe. Chapter 1, an unexpected danger. So you're probably already using generics if you use type annotations. Things like this. A list of integers, a list of strings, a list of animals. Mae'r bwrthynau hwn, mae'r cwyrdd ddwy'r cyn أis yn cael ei ddweithio'u ddaeth iawn, felly mae'r ddweithio, mae'r ddweithio hefyd yn ddyddolon. Mae yna brydd, mae'n cyfnodd y gallwn loson i'r ddweithio, oherwydd mae'r ddweithio hefyd wedi gwneud, mae'n ddweithio'n ddweithio'n oed, oedd e'n ddweithio'r ddweithio ar unrhywbeth ac mae'n gofynnydd ar glennydd Let's see how it works. So, here's an animal, we can call feed on it and it's going to say yum. I hope, can people see that at the back? Hopefully. So, now here we've got a generic. So, it's a function, feed the animals, it's taking a list of animals. And then it's looping through calling feed on them. And this is how we call it, with a list of animals. And it says yum yum yum. Now, is this code type safe? There's only one way to find out, run mypy. Yes, it is. Great. So, if you think about it, what's going on here is mypy's... mypy's can tell from unpacking the list that the thing inside it is an animal and then it can tell that you can call feed on it. So, this is going to work, isn't it? Oh. Can anyone see the problem? I hope I spotted it. We put an extra E in feed. So, this is nice, isn't it? I'm going to give this a little smiley face. Okay, here's another thing, cat. It's a subtype of animal. Let's feed the cats. That says yum yum yum. Type safe? Yep. No. Argument one to feed the animals has an incompatible type list of cats. Expected list of animals. What's happened? We've been walking happily on our mountain side path and mypy's just sort of tapped us on the shoulder and said, you're not on the path anymore. And we looked down and we're standing at the edge of a cliff or something. But it's not very obvious why you can't do this. I mean, the program runs, doesn't it? In order to understand what the problem is, we need to talk about the list of substitution principle. An object may be replaced with a sub-object without breaking the program. So in our case, an animal may be replaced with a cat without breaking the program. So let me ask, why is this a useful principle? Anyone? Why do we like the list of substitution principle? We're short on time so I'm just going to tell you. Polymorphism. Interacting with different types using the same interface. This is where your code might interact with an object but it doesn't actually need to know the exact type of that object. It just knows the interface that that object gives you, the methods that are on it. So this allows you to do things like this. You can loop through a cat and a dog and you can call feed on them. But it's a different type in each case. This is nice, isn't it? This is polymorphic code. If you couldn't do this, then you'd be doing things like this, which isn't nice. You have to check the exact type whenever you interact with it. So polymorphism is a very good thing that we want to encourage. It opens up lots of nice patterns in your code. But I thought you were meant to be able to replace a cat with an animal. So why won't my pie let us interact with a list of cats as if it's a list of animals? We need to go deeper. We need to talk about variants. Who's heard of variants? Yeah, some of you. It's a bit of a weird idea. It's a concept within type theory and it's a quality of a more complex type, like a list, which describes the subtyping that that list might have in relation to the subtyping relationships inside the list. So, for example, a cat's a subtype of an animal, so you've got three different kinds of variants and three different answers to this question about what the variants of a list is. The first one, a list of cats is a subtype of a list of animals. That's called co-variant. Or it might go in the opposite direction to the subtyping of the components. A list of animals is a list of cats. That's contra-variant. I should say if you don't know this notation like the open-headed arrow means a subtype of. Or maybe it's neither, in which case it's invariant. You can't replace a list of cats with a list of animals or vice versa. So, there isn't a right answer to this. Variance is a design decision. Oh, thank you. Got it. Variance is a design decision for a typing system. So, the Maipai developers get to choose what they want the variants to be. The thing that informs their choice is the thing we just learned about, which is the list of substitution principles. Maipai values the list of substitution principles just as we all should. So, it wants to make sure that you have to adhere to it. So, with that in mind, I'm just going to ask your intuition. We're then going to go through this. But what's your intuition for what the variants of lists should be? Should it be co-variant, which is where a list of cats is a subtype of a list of animals? Should it be contra-variant, where a list of animals is a subtype of a list of cats? Or neither. Who thinks it's co-variant? Hands up. Who thinks it's contra-variant? Oh, yes, someone. Who thinks it's invariant? A few. So, mostly co-variant. OK, let's first of all look at contra-variant, the weird one that one person thinks it might be. So, this is the feed the animals thing. Just for the simplicity, I'm saying, this would mean a list of objects, objects for a supertype of animal, a list of objects you can replace with a list of animals. So, let's pass a list of objects to feed the animals. What's going to happen? That is not going to work, is it? So, you can't substitute in a list of objects for a list of animals. Thank goodness, MyPie does not treat lists as contra-variant. It wouldn't be a very good typing system, so it's not. So, this is what most of us thought, but actually, MyPie's already told us that it isn't co-variant. It's already said that a list of cats is incompatible with a list of animals. That was the problem with our code, is list is not co-variant, but why not? Let's get a dog in the mix, we've got cats, and let's have a function here called increase. This is a bit different because it's changing what's in the list. It's going to add a dog to the end of this list of animals. Now, is this type safe? It is, yes, it's fine this. It's fine to add a dog to a list of animals. A dog's an animal. But what happens if we do this? What happens if we pass a list of cats to increase? There's now a dog in a list of cats. That isn't fine, is it? Dogs are not cats. So, this is fundamentally why lists are not co-variant. It's because they're mutable. It would be fine if they were immutable, but the fact that you can potentially put other subtypes in means that it just wouldn't work. There's actually, interestingly, quite an easy way to get the type checker to pass on this. You just annotate animals to say it's a list of animals. What's happening under the hood is, is looking at this list of cats, or this list with three cats in, and it's inferring the type from it. It's saying, that looks like a list of cats. And you're saying, I know it looks like a list of cats, but it's actually a list of animals. It just happens that there's three cats in. And so, I really don't mind if you want to add a dog to it because a dog's an animal too. It kind of makes conceptual sense, doesn't it now? If we run my pie on it, it's completely happy. So we just needed to annotate that to say, well, this is the exact type that's being passed in. Anyway, lists are invariant for this reason. And that's the case with other mutable collections such as sets. What about immutable collections, like a tuple? Let's adapt our code so that it takes a tuple instead of a list. It's going to run the same, but my pie is going to be happy with it now. So tuples and other immutable collections are covariant because you can't have that thing of adding the wrong type to it. Incidentally, this is a good reason to use immutable data structures over immutable ones. They'll type check more easily and it's more flexible. We're going to look at two others. Return types first. This animal finder returns an animal when you call find on it. So let's subclass it and then look at what we're allowed to do with the return types of these. So the cat finder finds a cat. The object finder finds an object. So if return types were covariant, we'd be allowed cat finders. If they're contravariant, then we'd be allowed object finders. Or maybe we're not allowed either, in which case it's invariant. So again, let me get your intuition for this. Who thinks return types should be covariant? Okay, a few of you. Who thinks contravariant? Even fewer. How about invariant? Okay, more, but I guess it's quite difficult to think about this stuff, isn't it? But I'll show you how. You can just think through it from first principles quite quickly. So let's first of all look at cat finder. We want to find out if we can substitute the subtype for the supertype. So we want to see if we can interact with a cat finder as if it's a supertype, an animal finder. And if we can, then it's type safe. So we've got a finder here. It just so happens that it's a cat finder, but it's an animal finder. When we call .find on this finder, it should give us an animal because that's like the contract of the supertype. Does a cat finder give us an animal? It does, doesn't it? It's actually fine. That is type safe. Let's look at object finder as well. Does an object finder give us an animal? Guaranteed. People are shaking their heads. An object isn't necessarily an animal. So that is not okay. So return types are covariant too. Actually quite flexible. You can narrow down your return types in your subtypes. Final one, argument types. Passing in. I've adapted the feed method to take in an argument food. Let's look at two options for our cat and the kind of food it likes. Maybe it's more picky. It must have cat food. Or maybe it's less picky. Maybe you can give it any kind of objectily anything. So that would be covariant. Hopefully you're getting the idea now. That would be contravariant with broadening the type that gets in. Or invariant. You're not allowed to do either. Last question, I promise. What's your intuition for this? Who thinks it's covariant argument types? Okay, a few people. Contravariant. About the same number. Invariant. More. I think most of you are keeping your hand down. Probably sensible. Let's think about it. Same principle again. We've got a cat. But actually, we want to interact with it like it's supertight, the animal. That means you can feed food to animals. It's not going to error. So can we feed food to the cat on the left-hand side? No, people are shaking their heads. We've narrowed it down by subclassing it. So we've kind of restricted the interface. So we can't any longer interact with that. So we can't do that. That's not safe. How about on the left? Can we feed food to the cat with the object? Is food an object? Yes. A couple of people nodding their heads. Weirdly, that is all right. It looks wrong, doesn't it? But it is actually type safe. So who would have thought argument types are contravariant? You can now start saying that to your friends. They'll love it. Okay, we're ready. Armed with all this theory to learn about custom generics. So I don't know about you. It feels like you should be able to do this, shouldn't you? Even if you think it through and understand the reasons why the type checker won't let you do it. You can imagine situations where you might want to narrow down the type. It feels a bit of a shame. What if we don't want certain types to be substitutable? What if we don't want a cat to be an animal? What happens if we're just using that animal superclass as a way of sharing code? But actually, you don't really care about polymorphically iterating over cats and dogs. And we're doing something different with our code. And if you think about it, we sort of already got this. So here's a picture of the inheritance of a list of objects with a list of ints and a list of strings. If you were paying attention earlier, you'll know that that is not true. List is invariant. You can't subclass it like that. So this isn't really how lists are made. Instead, we have lists as a generic. This is sort of non-standard notation, but I've sort of just used it to sort of say it's not really a supertype. It's more that it's just a type that's kind of floating out there and it's not a type at all yet. It's just a way of writing some code that then a type can make use of. So why don't we do this here? We could have this idea that we want. We want to model the idea of different cats and dogs, different animals being picky about different foods. So maybe we want to make animal generic, and then when we have the subtypes of animal, we bind them to a particular food that they like, cats like cat food. Dogs, they're less picky, they'll eat anything, even cat food. So this would be nice. This would be nice. Here's how to do it. It's all in the standard library. You have a type variable. It's called type var for short. And you might say, I don't like your variable naming here. T is a bit rubbish. Maybe so, but this is kind of the convention for naming type variables. So you just name this in the global scope. Typically you call it T, capital T. You give it this string T. It's just the same name as a variable. I do not know why you do this. Optionally you can bind it to a type that you want the type checker to make sure is passed in when you pass in a type to this variable. You'll see what I mean in a minute. Then you import generic, and so you inherit from generic in your generic class over T. So that T that you just defined in the module scope, you're going to then pass into square brackets, and you're saying this is the T that is then going to crop up elsewhere in the type annotation in this class. So you can see there we've removed food, and now it's that same T as the type that's coming in. OK, so that wasn't very much code, was it? Now, is this code type safe? Let's run mypy on it. Oh, need type annotation for animal. This is actually what we want, isn't it? We don't want animal to be a type. It's generic. It floats around. You can't do anything with it. It's just some code. So what mypy wants us to do is bind it via square brackets to a concrete type, and then it's going to become a concrete type itself. Let's do that. Let's make an animal and let's say this animal only likes cat food. Now, will mypy like it? No, but that's good because we're feeding it the wrong kind of food, aren't we? Argument one to feed of animal has incompatible type food, expected cat food. This is a good sign, isn't it? I'm going to give that a smiley face. Let's just check it works. Let's feed it the right thing. Yeah, it's happy. OK, now you don't have to do this square bracket stuff all the time. You can just do it when you create the subtype in the first place. So we're binding the cat food to the generic, and then inheriting it in one step here for the dog and the cat. Now, looking inside the cat, well, not literally, hopefully, we've got cat foods here, and these are kind of mirroring the generic type that it's inheriting from. It's sort of a mirror image of it, if you see what I mean. If we run this, mypy's going to be happy with it. And the nice thing is, is once you start doing this, you even find your IDE might be able to tell these things. This is PyCharm. I just started typing this and it just told me. I know this cat doesn't like cat food. That's quite cool, isn't it? So in summary about custom generics, if you've got this kind of inheritance relationship, sometimes you might identify that actually you don't really want to be able to substitute B and C for A at all. And you've just got some code that you want to share between B and C, in which case you might consider, oh, I don't have an animation for that, you might consider making A generic. That's basically the idea. Okay, we're ready to try and get a bit real now. Finally, in the case study. So I work for Kraken. We're kind of similar. Octopus Energy is an energy company, one of the biggest in the UK. We're opening renewable energy companies around Europe and the world. And our product for running the energy company is called Kraken. It does things like taking me to readings and sending people bills. And one of the challenges we've had is that the behaviour across different Kraken's around the world, there's a lot of shared behaviour, but there's also quite a lot of differences. So for example, customer registration, the model of a customer is a bit different in Britain, Italy, Japan. There's some similarities, but there's some differences. For example, in Italy you've got this idea of a fiscal code which the other ones don't have, but you need it to create a customer. And what we found is that how do you do type annotations for this? Because you sort of end up with horrible things like this. Because you can't like... Oh, sorry, that's right at the bottom. Basically it's just a star-star quag's any. And yeah, it's not very good. I'm going to put an invisible unhappy face that the front row can see. So if you think about it, it's like how would you type annotate that? How would you type annotate somehow if you've got fiscal code coming in, but only for some people and not others? And so we sort of developed this pattern called engines and contexts. So first of all you've got this idea of a context, which is like a data class which has all the data inside it that everyone can extend and put their thing in, like the fiscal code. And that plays with an engine which they can also extend, and it's got a hook for saving the custom context. And then this is how you interact with it. So you call this register function with the engine and the context. That's got the fiscal code in there, but all the rest of the stuff is like standard. I'm going to move through this bit quite quickly because I don't have much time, but basically that's what the register thing looks like. It gets the context and the engine. The registration engine is just a stub that you can use as a hook. And the registration context is just a data class there's a data in. This is the interesting bit. We subclass our registration context for Italy with our fiscal code and we subclass our Italy engine to take the Italy context and save the custom data. So let me ask you what's the problem with this design? Does anyone know? I'm just going to have to tell you because I don't have time. Argument types are contrarian. You can't do this. It's not type safe because you can't interact with Italy engines polymorphically with registration engines. My pie doesn't like it. So what's the answer? What we can do is we can say, well we don't care about polymorphism between these engines. We can make registration engine generic and pass in registration context as a type var. The basic idea is exactly the same as what we did with animals. So we make our type var binding it to the registration context. We subclass generic of T with the T there. Then we subclass Italy engine with the Italy context. I mean you're probably not going to remember all this but you can look at the slides later. There's a little bonus that you may put some T's in the entry point function as well. And we run my pie on it and it's happy. And if my pie's happy then I'm happy. So let's reflect on what we've learnt. Static type checkers are an amazing way of detecting errors in our programmes, silly mistakes, but also subtle breaches of the list of substitution principle that cornerstone of object-oriented design. And they're great but they can sometimes get in our way. And if we don't want to substitute things then we can maybe consider a generic instead. They're a bit harder to use but if we learn to use them we can open up new and exciting places on the mountainside whilst still remaining type safe. Thank you. Thanks David for a really nice presentation. So we have time for a quick question and answers. The microphones are on that side of the room. So if you can kindly just come there and if you have any questions that would be nice. I'm not that tall. My talk was great. I kind of feel like some of the issues that my pie detects is because it doesn't have all the information that a typed compiled language would have during compile time. So we are only doing some of those things because we don't have those things and my pie can't be smart enough. I think that so statically tight languages usually have generics for this very reason. So when you... I don't really work with many... I've been doing a bit of Rust recently for example and you absolutely do have generics all over the place with Rust. I think that you need generics. If you don't have generics and you have statically tight system then you're going to be quite annoyed. You need them really. Hi. Great talk. I wanted to ask when you're supposed to use new type because I've tried to use a new type to try and fix some of these my pie errors and it's gone very badly. So I wondered if you could explain. Maybe I've not used it. How do you say it? It's like from typing import new type. Okay. I don't actually know what it is. You can show me the dots afterwards and we'll chat about it. Nice. So I think that would be the end of the session. We have a Discord channel where we can still communicate regarding the session which will be North Hall, the name of this room. So thank you all for joining and let's have round of applause for David and have a nice day.