 Hi and welcome everyone to this session on type 4 development by Tony Morris and we're so glad you could join us today. Thank you. I'll just share my screen. Hello everyone. Thanks for coming along and sorry I can't be in India. So I'm here in my home office in Brisbane Australia. And my plan this evening or this evening for me is to talk to you about your 30 countries. That's pretty cool. Talk to you about just some workflows in solving some problems, some programming problems in this case using the Haskell programming language. So my plan this evening is to just because you know we're online and so on. I'm going to talk to you through my thinking as I solve these problems and I'm going to solve these problems as if you've never seen them before in your life. So these problems that you see here there they're available on GitHub. And I think there's a link there available if not just let me know. And what I'd be really I can see the chat we're having chat problems early but I can see that. So what I'd really like to do is if anyone has any questions along the way. You know how do you solve this or why this or anything please just put it in the chat. I'd be happy to take those types of questions as we go. Okay, so we're on a pretty limited time budget. And so I'm going to talk you through some problems. One option by the way is, you know, because I'm not there I don't really know your skill set for each of you in the audience some of you may have never seen a problem like this. Before in your life. So I'm going to start from the start. Maybe you want to see a bit of a hard problem as well. That's no problem just let me know and I'll skip over to that. Not hard but harder. So just to give you a bit of a rundown of what we're looking at here. We're looking at a GitHub repository contains a bunch of exercises. These exercises I developed with a bunch of other little few other people about 16 years ago, I think it was needed to build over time and you're looking at where it currently is. It's written in the Haskell programming language. So I'm going to assume to be that you're not familiar with Haskell. So I'm going to talk you through that a little bit. That is the repository that someone just asked. That's the correct one. So I'm going to talk you through these problems and I'm going to talk you through the Haskell as well as if you've never seen it before. If you get any questions along the way, please let me know. So what you're looking at here. So that this is on this line here line 14 to 17. I'm just going to explain to you what this is this is saying here. So this data keyword. What we do with the data keyword is we define a data type. No different to if you're familiar with Java or C sharp or anything like that. You define data types using the class keyword but in Haskell use the data keyword. And following that we have the name of the data type. So in this case the name is optional. And then we have what's called type parameters. After the after the data type name. In this case, there's only one type parameter. I'll call it a. So this is the data type name. And one type. Follow by equal sign. And then we have a list of constructors. These are called constructors after this. So the constructors are followed by the pipe sign. Sorry, there's pipe sign six in between the constructors. This constructor here. That's to say the name of the constructor being full. And so we put the constructor name and follow by that we put the, the arguments that go into that constructor. So like I was talking about before, Java and C sharp constructors in those languages have to have the same name as the data type or the class. But in Haskell that's not necessarily true. So we have this constructor full with one argument. And then we have this constructor empty that and empty has no arguments. So if we just kind of, let's just develop a little bit of an intuition for this data type. So I, when I say type parameter, you can think of it as like a generic or it's just saying optional of anything. It's, it's, it's a, when we fill out the type for this data type, it will be substituted. So just like you can have a list of integers, we could have an optional of integers. A stands for the integers part. So it's just saying anything. We can construct it with full. And when we do, we have to pass in one of those A's, or we can construct it with empty, in which case we don't have to pass in anything at all. It'll just make one. So one, one intuition or one way of thinking about this data type is optional is a list of A's. However, it has a maximum length of one. So that's one way to think about this data type. So it either has one A or it has no A's. So that's just one way to think about optional. There's other ways to think about it, but that's, I find that a pretty useful way. It's a list of A's with a maximum length of ones, or another way of saying that is it has zero or one A's. Now onto the type whole development sort of theme. Pardon me. I'm going, so that's the data type there. There's no exercise there. The first exercise to look at is this exercise here. I'll just have a quick sip. And the problem with this sort of notation is it's quite simple in terms of its concept, but the words when you try to explain it, it doesn't sound simple. It's always been a bit of a puzzle to me is how do I explain this so that it comes out simple. So I'll try my best. So you may think of this. So map optional is just a function name, just like you might think of functions in any other language. Double colon means has the type. And we're saying that map optional has the type and there's the type there. It's that big expression there. And the way to read this is we are first past a function and that function turns A's into B's. And then you pass an optional of A. And don't forget what optional is. It's a list of A's with a maximum length of one. And then it returns an optional B, which again is a list of B's with a maximum length of one. And our goal here is to solve this problem so fill out a function that has this type. So first of all, we're going to delete this and feel free to follow along on your computers at home or wherever you might be to solve this problem. But when I solve this problem, and like I said, I'm going to solve this problem as if I've never solved it before, I'm going to show you the tools that I think would be really, really helpful if you came across a problem like this before that you've, that's unfamiliar. And so I'm going to pretend that I've never seen this problem before and I'm going to use those tools to solve it. So first of all, is the first thing I do is I simply just delete that. That was just a placeholder for the exercise. And at this point, you might ask yourself, well, maybe you might say, look, I'm just totally confused. I wouldn't even know where to start. And the key tool. So you might have a little bit of a clue of where to start or you might just have none at all. Now, if you have none at all, there's just absolutely no idea where to start. You simply go for the underscore. All right, so you go for the underscore. Okay, you just type underscore. And then I'll just back up here a little bit actually. Okay, just to give you an idea of what. So here I am in the repository and I just typed GHCI. Now, in this case it won't, it won't, hang on, I'm just going to type GHCI and then I'll put the underscore in. And so I'll put the underscore in and I've just said, look, I have no idea where to start on this kind of problem. And so what we want to do every time we make the changes, we want to reload. Okay, so colon reload at GHCI. And when we reload it says, hey, you know what, this doesn't compile. All right, it doesn't compile. And I'm just scrolling up here. And what an underscore denotes, it's called a type hold. All right, so we said anything starting with an underscore is a type hold. And we just said, look, there's a hole in my code. I don't know what goes there. And this error message that you see down here is basically really trying to help you out to solve what goes into that hole. And it says, look, I found a hole and there's the type of the hole. You need to put something that has that type, right? Because we know that that's the type of our expression here. You need to put something that has that type into that hole. Something needs to replace it that has that type. So that's tool number one, which is just underscore. I have no idea, underscore. There are a few other things here that it tells you. I'm going to ignore those. Some of those are actually the answer. Sometimes it does just give you the answer, but I'm just going to ignore those. So we're just looking at the type hold here. Now, the second tool I want to give you is you see a type hold and it says to you if the type of the hole is a function, which it is, right? And it's a function because you can see that there's these arrows here. So the second tool is if the type of the hole is a function, then what you need to do is replace the type hold with backslash. So backslash means lander in Haskell. And then you need to give it a name. And I'm just going to give it a name or F or Fred. And then you type arrow and then you type underscore again. And you simply just do this mechanically. And if you do this mechanically, you'll get even more help when you go back down here to GXCI and you type reload. And we're going to have a look at the hole again. And it says, look, I found another hole in this time, but this is the type of that hole. So the type of the hole has changed. You know, it doesn't say this at the start anymore. So the type of the hole has changed. But the type of the hole is still a function. So I'm going to use that second tool again. I'm going to replace that type hold with backslash and then arrow underscore. I'm going to do that mechanically. And then I'm going to reload yet again. And now I'm looking at a type hold. The type hold is, well, it says it's anything of type optional B. So it's not a function anymore. So it's not a function anymore. We need to put something that has this type in that hole. I need to substitute some value that has that type into that hole. So, you know, I'll show you a couple of other things that will help you with. So the next thing it will show you is something called relevant binding. So relevant bindings are simply things that have values that are available to you. These might help you solve this problem. And it says, well, there's X and so there's X there. And X has that type optional A. And then there's F, which has the type A to B. And then there's myself. So the function is trying to solve. Maybe it needs to be recursive. I can tell you it doesn't need to be recursive. So relevant bindings is saying, hey, maybe these things will help you fill out this type hold. And then the other thing to look at is something called the valid holds fit. So what this is saying is it's saying, if you type this, if you put this thing into that hole, it will work. It will compile. Sorry, I should rephrase that. It will compile, but I don't know if it will work because the compiler doesn't know exactly what we're trying to solve, but it will compile. And if you have a look at this valid hold here, it's empty. It's saying, hey, if you just put empty there, it'll compile. But maybe that's not what you want. It doesn't really know that. We'll just scroll there. And just to demonstrate that, I could just put empty there. So empty, and then I reload. And it says, hey, everything compiles. Everything's great. Now, however, with this particular problem here, this is actually not the solution that we want. So there's some tests up here. So I'm going to copy and paste this test. There's a way to run the test, but just a quick dirty hack is to copy and then paste. So I'll paste the test in a GHCI. I'm going to run it. And you can see it didn't give me the same answer. It said empty rather than full nine. So basically what we want this function to do is, if this optional, this thing here, this X is a type optional. X is a type optional. If this optional has an A in it, so it's full, then we want to run this function on it. We want to add plus one to the eighth and get back nine. So what we want to do in this case is, we want to examine the optional and we want to say, hey, look, which constructor were you constructed with? Were you full or were you empty? We want to pull apart the optional, this X here. We want to pull it apart and say, hey, which one of these two things are you? So that's not the correct answer. It does compile, but it's not the correct answer. And the way you pull apart a data type is you do what's called pattern matching. Now, the quick dirty way to do pattern, there's a number of syntaxes to do pattern matching in Haskell, but the long-handed or just, you know, if you just don't want to think about all the other ways, is you just type case X of, okay. And just to get going. So when you say case X of, you're saying, well, is X one of these things? It's like a switch statement that you might see in other languages. We're just saying, hey, which of these, which of these ways was case, was X constructed with? So one way may be empty. And what you do then is you just put an arrow and then you say, in this case, I'm just going to say, I don't know. And in this case here, we're going to say full. And I'm also going to say, I don't know. So we basically split the X out and we said, hey, were you empty or were you full? In which case this A, by the way, is not the same A here. So I might rename it just to make that clear. Pull out anything you want as long as it starts with a lower case character. And these typos, by the way, they can say, they can be called anything as long as they start with an understore. So I'm going to see how these type two typos are both understore. I'm actually going to name them both. I'm going to say there's the empty typo and there's the full typo. And the reason is it'll give me a better error message. It'll give me messages for both the two typos and it'll give me their names so I can know what to do about each one. So again, I'm going to reload. And when I reload, I'll just scroll up to the top and it says, hey, look, I found this hole, this type hole. It was understore empty. And it needs to be an optional B that goes there. So let's have a look at our other sort of helpful things here. It says, well, there's relevant bindings. There's X of type optional A. That's actually not going to help us because we need an optional B. And then there's F, which is A to B. And then if you have a little bit of valid holes here and this is one point of confusion that I come across quite often because I've just told you that we need an optional B. But empty has the type for all A optional A. So is that a type optional B? And the answer to that question is yes. And the reason is because of the position of this for all here. And it's saying it's just anything at all. It's not that A. It's any A at all. It's just it's not that A. I pointed to the screen. You can't see me doing that. It's not that A there. It's just anything at all, including B. So one point of confusion, like I find regularly and I don't have a good answer to trying to help you with it other than just to try to really explain it is these type variables have scope. Is that A the same as this A and so on? It's quite difficult to point it or explain it. It's with some practice. You do get an intuition for it though. It's a bit like, you know, if you're using Java or C sharp and you just had generics all over the place but you name them all sometimes the same thing. It'd get a bit confusing in terms of the scope of each of these things and it'd be hard to talk about. You might say this A, well, which A, this one here that was declared here, not this one declared here and so on. But so I'm just going to sort of tell you that this A here is a different A to this A here. And the for all is saying it's just absolutely anything including B. So empty does fit there. And actually that is the correct answer. Because if there are no A's in my optional A, well then I just don't want to return any B's. I'm just going to return an empty list of B's. So empty fits right there. It will compile. I'm just reloading and have a look at the next typo. And again, I need to make an optional B. I have a look at my relevant bindings and it says I've got an additional value here which is A, A, A, A, or R. If you want to call it that. It has the type A. And when I say that, I do now mean that A. I have X and I have F. And I have myself my optional. Again, I get valid hold fits empty. Now in this case, I don't want empty because what I want to do is I want to examine these relevant bindings. So just just to be clear here, this will not always give you the answer. It will just really try to guide you as much as it can. So it says, well, I have an A right and I have a function and that function turns A's into B's. Cool. That would give me a B if I use those two values. But I actually need an optional B. Okay. So I want to give you sort of a thought process that you may find yourself in some time, which is you might sort of stare at this and go, hey, look, I can get a B, but I don't think I'm quite at the answer yet. So it's like you've worked out one of the puzzle pieces, but you haven't quite worked out the entire puzzle yet. And I want to give you this tool, which it'll just be another type hole. I want to give you a tool to help you express the piece of the puzzle that you've worked out while just leaving a type hole with a bit that you haven't yet quite worked out. So the mindset and I'm just imagining a particular mindset in this case, which is I can get a B. I know how to do that by putting this A into this function, but I don't quite know yet how to get to an optional B. Now, and one way of writing that is the code. So I'm going to write this in code using type holes. Okay. So you know how to get a B, right? Because that is a B. I've passed in the A. It was its name was AAAA. And I've passed it into the function F, and F turns A into B. So yeah, I had a B, but I know that that's not quite the right answer. What I want to do in that case is I want to just put a type. So I want to put a type hole. So I don't know how well you can see that, but there's another store there. Something. Okay. Something. And I want to put parentheses around this so that it gets. So I'm saying something goes here and it does something with the B, but I don't yet quite know what, because I've worked out this piece of the puzzle, but not this piece here. And then I'll reload. And it says, look, you've got this type hole here and it's type is a function B to optional B. And as we know, if I have a type hole that is a function, I could simply just start typing backslash and name and then an arrow. But I'm going to skip that step because we, well, I'm not going to skip that step. I'm going to just have a look what happens below first. Okay. So here's my relevant bindings, the AAAA that I've got. I still got F, I still got F, and I've still got myself in that optional. So I've looked at my relevant bindings and like, you know what, that is not going to help me make that, right? So if you just kind of stare at this and you're like, yeah, that's not going to help me make that. Okay, cool. All right. Well, that wasn't very helpful. Let's have a look at ballad holes now. And I just realized there'll be three of them there. I was expecting one, but I saw three. But there's one, right? So the full constructor will take anything. And again, this is including B, and it'll make an optional B. So I could take that code and I can literally just simply copy, oops, I pressed the wrong button now. I can simply copy and paste over the top of the typo, reload, oops, reload, and it compiles. Now I'll rerun my test. Yeah, my test works. So does the other one, by the way, this one should work. That one's actually impossible to fail. And I would declare a victory at this point. So look, I've solved this problem, and I did it using type holes to help me get there. Yeah, ballad hole fits. It's one of the most useful tools I've just seen come in. I cannot stress how useful that tool is actually because this could be day one Haskell for you, right? And this would be a pretty good exercise for you to have a go at and try to crack it using type holes. But I just want to reassure everyone that every time I write Haskell or any code at all actually, but preferably Haskell, I am using those type holes. I will look at the problem and go, yeah, that problem doesn't fit in my head. I will use type holes to get me to that answer piece by piece until I get the answer, and I'm satisfied that's the correct answer. It's a mindset that comes, it's kind of a consequence of functional programming. You can see we're not doing any side effects here, and you can't do that in Haskell at all. So using those type holes is, I think, one of the best ways, particularly for a beginner, someone who's only just starting out, just looking at those type holes using those tools and just mechanically following those rules, you will quite often very reliably get yourself to an answer. What I'm going to do is I'm going to solve, actually, no, I'm going to keep ranting a little bit about this problem here. There's actually a little bit of mathematics. I'm not going to show it to you. I'm just going to promise you it exists, and that's just due to time limits. There's a little bit of mathematics that I could do, and I could do it on the type, on this bit here, actually. I could take this type, and I could do some, it involves some arithmetic, a little bit of type theory, but nothing too complicated. It's mathematics that I'm sure that anyone can do, and by arithmetic, I mean, you know, addition, multiplication, exponentiation. It doesn't really get much harder than that. A few little theorems and letters and so on, nothing too difficult, but I could do a calculation on this, and I'm going to sort of, I'm going to skip the calculation, and I'm going to get to my answer, and I'm going to tell you the answer. The answer is two. The number two is the answer that I would get if I did that calculation. And what does this mean when I come to the answer two? Well, that means that there are only two things that will ever have that type. There are only two possible answers that I could put here. All right? And by that I mean, well, we looked at the wrong answer. All right, let's just go back to the wrong answer. Comment this one out. And there's one answer. Reload. That compiles, but the tests don't work. So this is one answer. And this is the other answer. So another tool, and you'd have to practice a little bit on the mathematics, but I really want to promise you that it's quite easy once you get the hang of it. It's just some arithmetic and a few little theorems. You work out, hey, look, there's only two answers to this problem. Two possible answers that have that type. Ignoring the tests. There's only two things that have that type. I found the wrong one. It must be the other one. And so because it's two, and the test failed, I know for certain that this is the correct answer. There's no other possible answer. And two, so, you know, like I would encourage you to have a look at mathematics. It's called free theorems or theorems for free. But another really good exercise, and this is one of those exercises that is better than you probably think it is, which is to try to make that not true. So, like, just develop a little bit of skepticism and just go, look, you know what? The guy told me it was two. I'm just not going to believe it. I think it might be three. Cool. Okay. Now that you think it's three, you just, or three or more or something. The goal now is to put something in this position here. That is neither of those two answers and it compiles. So I'm just, I'm making a promise to you. The promise is you can't. But when I say that, I don't want to discourage you from trying anyway. Do try it because by trying it, you'll find that you can't. But by finding that you can't, you'll develop an intuition for what it means to use types to get you to your answer. There are only two ways to solve this problem with that type. And to me, that is an incredibly invaluable tool because you and I, let's suppose you and I, well, first of all, we changed India. Everyone is writing Haskell now. We'll do that one day, right? And so we're all writing Haskell and you and I were on the same team. And I had the day off yesterday because, you know, this wasn't well or something. And I come in the next morning and you're not there, but you've left me this code, right? And I'm like, oh, you know, what were they doing yesterday? Now to help me work out what you were doing yesterday, I can kind of look at this type. I can get my notepad out and do some maths on here. There's only two answers. They did one of those two things. So to me, that is a really useful tool. And when I say you and me, sometimes it's just you yourself, right? I wrote this code a year ago. What was I thinking, right? And the answer to that question is one of two possible things. That's what you were thinking a year ago. So, you know, I was working on code just the other night that I wrote 12 years ago. You know, I looked at my gallery, jeez. I wrote that 12 years ago. What was I thinking again? And yes, I just use that tool. And it really helps me kind of get up to speed and work out what it is that I was thinking that day 12 years ago. So this is how you solve this problem. Just looking at the time. I'm going to skip ahead. I'm not going to solve this one just due to time. That one's a really good exercise. I'll tell you the calculation. The calculation is two. There's only two possible answers for that one. Follow the types, use type holds. I bet you can get to the answer. I'm just going to show a hard one. I would say a bit of a harder problem. And I'm going to show you this problem. You know, because I'm sure there's some of you. Maybe you are a bit more familiar with Haskell. But you're finding intimidating. I think everyone's capable of practicing and getting to this ability. But yeah, I'm going to work through a more difficult solution, difficult problem. So I'm kind of fast forwarded a little bit. I'll explain a little bit of syntax for you. I'm just going to find a problem, actually. You know what? I've changed my mind. Yeah. I've changed my mind. Okay. I'm going to make up a problem. It's very similar to the one that we saw back then. There's a lot of syntax that will be unfamiliar potentially. So I'm just going to call a problem. I'm going to make one up. And we're going to type here. Okay. So problem. And I want you to imagine you came across this and said, you know, I believe in you. You can do this. You can solve this problem. So let's reload. Make sure it compiles. It compiles. The key to compiling is just this OK down here, by the way. So yes, it's solved. Sorry, it compiles. But we need to solve it. I'm going to talk you through my thinking again. So, yeah, I have looked at that, yeah, with Jim. Yeah. So the connection between manual typehold development and programming or pre-searching with Jim. Yeah, I have. It's quite interesting. So yeah, there's a, if you take this sort of, this mental model of thinking that code, there's a lot of rabbit holes ahead of you. They're all beautiful and elegant in their own way and some very practical and useful. And that's certainly one of them. Yeah, that's for sure. So just to give a bit of background for the people who aren't familiar, Jim is a tool written to Haskell. And what it does, have I even got it on this computer? No, sorry, I'd show you if I did. So what Jim does is you give it a type. You just pass it. You say to Jim, hey, here's a type. And it comes back and says, well, here's a program that has that type. So it fills out the code for you. So it is related to what we're doing here. And there are other tools. Jim is one of them. So yeah, type it into Google if you like Haskell Jim and knock yourself out. But it does, it does, it's called program searching. It finds a program that has that type. So, okay, let's look at this. Let's have a look at this type. Okay, so the problem with this notation, like I said, is it's hard to say in words. It's really hard for me to say in words. But it is, it is quite simple. I'll do my best in words just to give you an intuition. So it says, if I take a function and that function turns B's into C's. And then I take another function and that function turns A's into B's, then return a function. Thank you for the time check. So, and then return a function and that function at your return should turn A's into C's. Okay, so even a function B to C and a function A to B make me a function A to C. Cool. All right. Again, it's day one. I've never seen this problem in my life. I have no idea where to go. Where to start. So I go underscore. And underscore is just, I don't know. I mean, I have no idea. I reload and it says, well, I found a title. It's called underscore. And you need to make something of that type. It goes right into that title. Okay. And you stare at this and you know, what does that mean? Well, it's a function, right? It's a function. It says right there it's a function. And so you pull out that other tool that you've got in the pocket. And that tool is, well, the type. Backslash and then another underscore. Yeah, correct answer. Nilesh, correct answer. So yeah, you start with a lander. The backslash is called a lander by the way. If you go and look up the Greek lander symbol, the backslash is the closest axis character to it. So that's what Haskell uses. It uses backslash the lander. And then we reload again. And we have a look. And it says, hey, you know what? I found a type hold for you. And it has that type. You're like, oh, okay, cool. Well, that's a function. I hope you all agree. So I'm going to follow mechanically. And I'm going to type that in. And then I reload. And it goes, I found a hole. And it has that type. And that type is a function. I can see that it's a function. A to C. So reload. Like, oh, does this go forever? The answer is no. I found a hole and it needs to be a C. I need a C. A C goes in there. Okay. I need to make a C. Hmm. How do I make a C? All right. Let's have it with the relevant bindings. Okay. So I have an A. I have G. G is of type turns A's into B's. And I have F. And F turns B's into C's. And I have myself. There's no valid holes. Okay. Wow. How do I solve this problem? Now, I see that my hole is a C, right? And if I look at my relevant binding and say, you know what? I would have a C if only I had a B. But I want you to imagine that you've identified just that part of the problem. You have absolutely no idea where you're going to get a B from. I've worked out that if I pass something into F, I should get a C. All right. So you go, here's F. Here's something. I should get a C. And now it says to you, you know what? I found a hole. And the hole is of type B. So I need something of the type B to slot straight into that hole where the B underscore is. And we're going to have a look at our relevant bindings. And our relevant bindings where we have A type A, we have G type A to B, and we have F type B to C, and we have ourselves. And there's no valid holes. Damn. Okay. Let's have a look at our relevant bindings. So just refreshing again on what we need. We need a B. I need to get a B from somewhere. And when I have a look at G and I look at G and I go, if I could just give it an A, it'll give me a B. I want you to imagine again that you've just worked that out. I just, I found G and if I give something to G, it will work it out. So I'll just colon R by way of short for reload. I was going to try not to do that, but I did. So colon R is short for reload. So I reload and it says you have a hole. The hole is of type A. And I look at my relevant bindings. And well, that one looks pretty useful. And actually valid holes. A will go there. So copy paste. And then I reload and yeah, it compiles. So one thing to think about here is, do I have the correct answer? Is this the solution? There's no test obviously. I didn't write any tests. But is this the correct answer for this problem? And the answer to that question, well, it really depends on the tests. But it also depends on something else, which I want to talk about. But I think is more important than tests. Tests are the last result. Types are where it's at. So I told you earlier that if I did some mathematics on this, I would get to the number two. And there's only two things that have that, two possible functions that have that type in the entire universe. So I showed you the one that doesn't quite work. And I showed you another one that does work in terms of the test. So this one here does need one test. This one here, by the way, is redundant. It's impossible to fail that test. But this one here, it needed that one test because the answer was two. To disambiguate and say, well, which of the two possible answers is the one that's the correct one? It's the one where that test passes. And that disambiguates which of the two possible answers is the correct one, this exercise here. Now, this exercise here that we've just done, when I do the maths, and again, I encourage you to do, maybe when I come to Bangalore next year, I'll hang out at lunchtime and we'll get our notepads out and I'll show you. But when I do the mathematics on this one here, the answer's one. So it comes to the answer one. There's only one possible function with this type. And because of that, I can now conclude it must be the correct answer. There's no other possible answer. So if it has that type, that must be the answer. There's no other way to satisfy that function's type. So I've just noticed I'm running out of time. So I just wanted to help you out with that. And yeah, that's exactly right. Yeah. I wanted to say, I know that a lot of this stuff can be quite overwhelming for some people when they first get into it. And it looks like when you watch someone who's experienced doing these kinds of problems, it looks a little bit sort of wizardry. But I want to break those steps down for you and just say, hey, look, these are the steps that those people are going through, even for hard problems. And I encourage you to follow those steps as well. And I would happily help anybody do the same. But yeah, hopefully next year, I can, you know, if you need help, send me an email, of course, but hopefully next year when I get to Bangalore, we can hang out and do it together. So yeah, better wrap up. Just got me out of time. Well, yeah, thank you, Tony, for this great session.