 Okay, now it's 10 passed. Yeah, maybe wave your hand if you are around. I mean, okay, there's one hand up. All right, good, we have some people. So let's move on to the functions and struct section. Just closed my demonstration notebook, here we are. Okay, so if you've been bored for the previous two hours, this is where it gets interesting. If you want to know how Julia is different from other languages, we'll go into a lot more detail here. These functions really are the thing where Julia is different and where it does things well. So let's get into it. Oh, well, okay, so if you are relatively new to programming and if the first two sections were not too fast, then what you need to get from this section is how to define a function, how to define a struct is also very useful. And actually, that's mostly it. So how running functions works. So it's multiple dispatch, it's not exactly, it's not the most complicated thing, but what you really need to know from it is that when you can run a function with a given set of parameters. Okay, so Julia is kind of a functional language. It doesn't have a lot of data types. We can declare our own, but functions are the basic building blocks. So basically any other programming language has functions but a fortune calls them subroutines. They can also be called procedures and I'm gonna see you have blocks and so on. In Julia functions does the basic job of all of those structures, okay? So a function at its core is something that takes in some arguments and returns something. So or does something with those arguments so the print function is a function. It takes some arguments and it prints them. PrintLn does basically the same thing and also prints a new line. And we saw the push function, which takes an array and put something at the end and so on. Okay, so that is the basic idea. So this is how you define a function at least most of the time. This is the clearest way, but also the way that takes most space. So let's define a say hi function. It takes as an argument a name and a lot like the print function itself. This just prints something and doesn't really return anything that's useful. So it prints hi and now we'll do some string extrapolation, interpolation, even though we'll actually talk about it later. So this will put name inside the string. Let's say nice to meet you. Okay. And then the function ends with the end keyword. Okay, let's define that. And it returns the name of the function which is a generic function with one method. We'll get back to that. Now, generic part means you can call it basically anything. We can call it with the name. A little bit look out of course as well, but it doesn't really have to be in a string of course, but it doesn't have to be even a string. So we can call it with the number two or 2.0, which is a float or even a fraction or with any other data type we may define later. So because we didn't specify what name is, it can be anything. And as long as it works, it works. So this is called also duck typing in programming language or new functions. So if it walks like a duck, if it walks like a duck, then it's a duck. If it goes here and it works, then fine, we run the function. Okay. So a second example function. So again, define a function with the function keyword. We'll call this function F and it takes a parameter X. So this is in mathematics notation, you always your first function is F. The second one is G and so on. It always takes the parameter X. Okay. What we will now return something and what we will return is the square of X. Okay. And again, this is a generic function. So it works with anything. And let's take the wrong window. Let's take F of 42, just to check. Okay. We get something. That's probably a square of 42. All right. Now immediately we have, because of course defining a function is an important thing to do. We have two exercises for defining a function. And let's give this at least five minutes or as long as it takes for basically everyone to do this. So please do exercises one and two. And if you already, I mean, yeah, if you have the green reaction button, remove it or an edit when you are done with the exercise. Okay. So we need to define a function called add one. So we do that with the function keyword. The name of the function goes here and it adds one to its input. So it needs to have at least one input parameter. Let's call it input. Return something, return to use the return keyword to return something and input plus one. So I assume by add one input, that's a spelling mistake. By add one, I mean run with the plus sign. And then the end keyword. Okay. So this will work. We can also test it. Okay. Yeah, we can test it. So add one to one, returns two, so seems to work. You can also omit the return keyword. So in Julia, this is important to know just in case you are expecting a function not to return anything, it will always return the result of the last statement. So it's important to verify that the thing that it's returning is what you actually wanted to return. Okay. So this will still return input plus one. That's actually one of the main source of fights about the style, like whether to use or not for the return. Yeah. For the last, there are people who strongly says it's better people who strongly says it's better without. So yeah, it's, as long as you know what it's doing, everything is fine. So I like to be explicit and have the return keyword so that it's very clear what it is returning in the end. But of course, if you know that the last line is always returned, then you never need the return keyword. You always know what's going on. Yeah. Now this is another function and it needs to be poly two, oh, poly two for second order. Okay. I was wondering if I'm missing something. Poly one, yeah, poly two that takes in the argument X. And it needs to evaluate four. So return something four plus three times X, but we don't need the times here. Actually plus two times X squared. And end with the end keyword. A, N, D for N. Okay. Right. And let's try this. So call it with a number. Five point zero, okay. Poly is not defined because it is called poly two. Okay. Yeah. So if you try to call a function that doesn't exist, you will get undefined variable error. And this is a good place to look at the stack trace as well. So when you get an error, you always, you basically always get a stack trace. It tells you where the problem happened, including the line number. So this is much more useful, of course. If, so this is, okay. This is much more useful if the problem is in the file. If you are writing the code and putting it into a file, because then it will tell you the name of the file and the line number where the problem happened. Here for us, it's telling that it happened in cell 12. That's in 12. That's the, yeah. That's the name of the cell. And then this is, well, this is also the line number. So it is actually completely accurate. Okay. It tells you exactly where the problem is. So the first line here, this section one, is telling you, well, on the top level, where you were running your code, where the problem happened. It was actually calling this. So there is a second level. It was calling a function that's in this boot.jl. But we don't have to go there. We can figure out from here what happened. So I didn't write this code. So I probably don't need to inspect it to figure out the problem. And there's a third level. This is Julia base. So, yeah, this is the Julia interpreter really, at this point. So I definitely don't want to go inspecting that to figure out my problem. Sometimes though, the problem is in a library and you actually have to go there. So here it was, the number two was missing here in the function name. Okay. Then this is asking us to expand the function. So we can define poly2 again. But this time it takes x and this co-fs parameter. So it takes two parameters. And co-efficient co-fs is a array of length three. Okay. I don't necessarily need to do anything, except assume it's an array of length three. I could check that it is in fact an array, but we'll do that later. So this should now return. It should return the result of the polynomial, but now assuming that the coefficients are in this co-fs parameter. So co-efficient one plus co-efficient two times x plus co-efficient three times x, x squared, okay. So again, in Julia, numbering in an array starts from one. In many languages it starts from zero, but also in many languages it starts from one. Okay. So again, this is an array of length three. Okay. So now we have defined a new version of poly2 function. Just test it. So for 42, and let's do x is 5.0, the same as above. And let's give it the same coefficients as well. So this needs to be an array. And the coefficients are three at four, three and two. Okay. So it gives the same result. So it seems to work. Okay. But yeah, this is a matter of taste and a matter of if your collaborators understand what's going on or you in 10 years, cause you have to think of you in two years is the person who is the most likely to be confused with your own code. Okay. Right. So there's ways of defining a function in a single line especially if the function is only one line then it often makes sense and it can be clearer. So we had the say hi function here, which is just one line and it just brings something. We can do that in this one line function definition. So this is say hi two, a different function with a different name. And then we just, we basically, like we're calling the function, we have say hi and the in parentheses, the arguments, but then we just assigned that to that. So equals assigned with an equals sign and then one line of Julia code. So this print a line statement and this will define a function called say hi two. So just to demonstrate that it works. Albert needs to be a string. Okay. Okay. So it works. And similarly for, so this F two, which takes a square, we could have defined in a single line. So this is especially, this is much more readable really, than this one is F. But of course, if the function has multiple lines, if it's doing complicated things, then this is not a good way of doing it. It's just for short one line functions. Anonymous functions also, I mean, they exist, they can be useful. So this does exactly the same as this F two, but it doesn't give any name to this function. It defines the function, it's generic function with one method, just like this one, but it doesn't have a name. So you cannot call it directly, but it can still be useful. So if you define a function that takes a function as a parameter, like this functional square and what this thing does is it just calls the function twice. Then you don't necessarily want to need to define this function separately. You can just give it here and this works. So this is mainly what anonymous functions are useful for. You give them as a parameter to another function. And if you never need it later, then it works. Okay. So yeah, back typing. This is just saying that you can call it with any type. So we can say hi is not defined. I have never run this. Okay. So now say hi is defined. Okay. So yeah, you can call it with anything. I already mentioned this. It can also, it can even be called with a matrix. So I could call say hi with the matrix. The result is kind of weird. Yeah. It kind of puts the matrix in one line using this space and semicolon notation. Okay. But it's much more, it makes a lot more sense to call F. I didn't define F either. Sorry. Okay. Now F is defined. And now I can call F with that matrix. Okay. And there we are. So yeah, as long as you can evaluate the function with that type, it just runs with that type. This I already mentioned. So there's mutating and unmutating functions with, and mutating means it changes its arguments and unmutating means it doesn't change its arguments. So it's just a convention, but by convention we add a exclamation point to the name of mutating functions. So another demonstration. So V is a vector with elements are not sorted. There is a function called sort. And it doesn't have an exclamation point. So you know it doesn't change the vector. So it returns a vector where the elements are sorted, but the original vector is not sorted. Okay. There is also a sort with an exclamation point that changes the vector. So it still returns the sorted vector, but the original vector is now also sorted. So is that clear? I guess this is a very quick exercise. Let's do a poll. Do you think, or can you figure out if there is a function called push without an exclamation point? Do some, the X, the red X to say no, and the green check mark to say yes. I think we have, we are arriving at a conclusion. So the fastest way to me, and this is, but yeah, this is the fastest way, but maybe not the best type push. See, there's nothing. Okay. Push not defined. So there is no function called push without the exclamation point. So for the function push to make sense, it really needs to modify the array. So yeah. So this actually adds something to the end and this one doesn't exist. I guess it could return a new array with the element added at the end, but there is also an append function. Is there an append without? No. Okay. Sorry. At least you can add it like this. No, dimension don't match. There's the cut at least for concatenation in Julia. The function, if you want to concatenate the two arrays, the vertical cut. V, yeah, V, yeah, sorry. Okay. Okay, but yeah, I guess. So push, if it returns a new array would kind of make sense, but it doesn't exist. Okay. All right. So now a quick introduction distracts before we go into multiple dispatch. We have time. Good. Yeah, I think we have 20 minutes before the next break. Yeah, that's an interesting question. Can a function be defined inside a function? I mean, yeah. So you can define functions in another function and that function then doesn't become defined outside the function. You can also pass functions as parameters like we did in the functional square example. So functions are basically variables just like any other variable. You can pass them and you can, you can't really redefine them, but yeah, you can pass them as parameters. You can define them inside another function and so on. In the wrapper, you can redefine those, but... Yeah, here you in the notebook, you can't. Okay. I mean, you can define with a new set of parameters. So yeah, abstract is in principle a very simple thing. It's a collection of data wrapped inside a name. So let's say, okay, for a good example of where this would be useful, we can scroll down for the epidemic simulation. So a plant in our simulation contains an infection status. So whether it's infected or not, or if it's recovered or anything else, immune, and the time it's been infected because it recovers after a number of time steps. So a plant has at least two things associated with it and it makes sense to wrap them in one object, right? So that's the situation where structs are useful when you have some related things that you want to wrap in one object. They're also useful when you want to redefine a function. So here I'm defining my integer and in a moment we'll redefine some functions to work differently when you give a my integer to them, right? So, but yeah, that's multiple dispatch. That's a very important topic that we'll go into in a bit more detail. So to define a struct, you use the struct keyword and I should be typing this out at this point. So my demonstration page instead of the actual notes. So we'll define a my integer and what my integer really just contains an integer number. So it only contains one thing. We could have a number and whatever properties my integer would have skewness, which is a flow, abstract flow, and end with the end keyword. Now, yeah, but it doesn't have skewness. It's a property that something could have, but my int doesn't have that property. So it just has a number. Okay, and now it's defined. You can create a new variable of type myint by basically calling this struct name as a function. So we call myint and the number of parameters is the same as the number of things inside the struct. So we'll call it with one integer, okay, yeah, integer five. Right, and well, now myint is a new object of a new object that contains this integer five. You can directly refer to the thing inside to the number, but one thing you cannot do is modify it. So I cannot directly replace the number inside with another. An immutable struct of type myint cannot be changed. Instead, I could just create a new myint variable and assign it to the same name myint and just replace it. Okay, so structs by default are immutable. So the insights cannot be changed once it's been defined. You can though create a mutable struct. So you add the word mutable in front of struct my mutable int and let's just say it contains an integer called number like this, okay. This type specification is used a lot. So the name of the variable and then two colons and then the type. So well, we'll get back to functions, but a function parameter, you can narrow down the type of function parameter like this. And yeah, you can also use it in the struct, okay. Now, if I define my mutable int is, and now the tab is also useful here. So I start typing something and I press tab once to complete it. So let's put a five in there, okay. So now I have a my mutable int using tab again. I can print it and now I can in fact, I can refer to the number with the dot syntax and I can replace it like this, okay. So just to check my mutable in now contains the number two, but integers are not mutable. So you cannot say five equals two. That doesn't make sense. So really you shouldn't also, you shouldn't be able to replace my int five with the number inside with something else. So I would rather use the my int later rather than my mutable int. However, if you define a plant for the simulation, you want it to become infected and behave, well, I mean, you wanted to change your simulation. So we will make it mutable. Before we defined the plant though, there was the infection state. So it can be infected, it can be recovered, it can be just starting state, uninfected. And for that, this enum keyword is very useful. So enum defines a new style, very much like a struct, but it only has a set list of possible values. So here we, well, just enum keyword. This means, this thing means it's a macro, but if we have time, we'll talk about macros. So we define a type called fruit. It can be an orange, a banana, apple, pineapple, or a lemon. But it cannot be anything else, pineapple. Okay, that's written correctly. So fruit is an, well, it prints all the possible values of fruit. It's a type. So let's do my fruit and assign it the value apple. Okay, so now my fruit is, well, my fruit is an apple, but I mean, if you type two, it will print the value two, or if you type A equals two, it will print the value two. So in the same way, this actually prints apple. But it then specifies apple is of type fruit and its numerical value is two. So the way enum works, it assigns all of these in numerical value. Here it's actually start from zero, which is, yeah, that's just how it works. It's actually an integer, that's why. So an orange is zero, banana is one, apple is two. Okay, and then you can also check the type of my fruit and it prints the same thing as here. So it is of type fruit. So let's do an exercise before they know the next exercise we need the first two, the epidemic simulation. Yeah, okay. So for the simulation, like I mentioned, we first want to define a list of possible states for the cell. So it can be uninfected, which is the normal starting state. I mentioned immune, and in the first page we have some immune cells. So it can be immune. It of course can be infected. It can be dead or it can be recovered. Okay, so now we have, oops, I made a mistake. I probably cannot undo this without restarting the interpreter. Now there is a new type called uninfected. That's not what I wanted. I want the type to be called infection status, but now all of these names are reserved for something else. So this probably doesn't work. Yeah, it doesn't work. So I will go here and I will restart the kernel. That was actually also asked a few minutes ago in HackMD that if I do a typo when I define the struct or want to change the definition, what can I do? And while for the way Julia works internally, you cannot type, is it like that? So you just need to kill the session and start it again. Yeah, of course. If you are doing serious programming and writing stuff into a file and you change something and then rerun it, you are always restarting Julia when you're rerunning it with Julia and the file name. So then this is not a problem, but in the notebook and in the REPL, sometimes you just have to kill the REPL and restart it on my lab. Yeah, I'm going to change this to infection status because that's what I have in the notes and I will get confused. So, yeah, I cannot. Okay, let's restart again. Okay, so what happened is that this, the names of the states also are reserved and cannot be defined as something else. Okay, all right. So now we'll do a create a struct that contains the needed data for the plant. So it's a mutable struct called plant and it contains the status of the plant, whether it's infected or something else. And now we want to specify that that is of type infection status, infection status. Okay, so it needs to be unaffected, immune infected, dead or recovered. Those are all the possibilities. There is also the infection time, which will contain the number of time steps in the simulation since this plant was infected. And, okay, so if it's not infected, then this number doesn't really mean anything, but if it is infected, then it means how long it's been infected. Okay, and now we can define create new plants. So we'll create a plant in the state uninfected and zero because it's not infected. So we'll just give the number zero. So the first thing goes to status and the second thing goes to infection time. Okay, let's create another plant. Let's make it infected. Let's say it's been infected for one turn, one step in the simulation. Okay, and we can inspect the contents, plant1.status, okay. So now we have a big exercise. So let's give it at least seven minutes until the break and then maybe a bit more time after the break. So this is writing a function but a somewhat complicated one, of course, yeah. I have a skeleton structure for it. So maybe it's not as big as I was sort of immediately thinking. So it needs to do, well, it takes three parameters. It takes a plant. So some, a variable of type plant. It takes the time it takes for a cell to recover and the probability of an infected plant dying in one turn, right? So every turn, this plant gets updated and when it gets updated first, if it's not infected, then its state doesn't really change. The only thing we're really modeling here is the infection. If it is infected, there is a probability that it dies. This is something that this if statement I already written here. So to do something with a given probability, we create a random number. So we already saw this random function and we just do one number and we'll take that number from the array. So this kind of weird looking expression but it just takes one random number. And then if that happens to be smaller than the death rate, the death probability then the plant dies, okay? The every time step, the time the plant's been infected the infection time increases by one. And if that time is higher than the maximum so there is a recovery time given here that's a second variable. So if it's higher than the recovery time that plant recovers. So that's all, well those three steps those three things go into simulating one time step for one plant. It doesn't include any interaction between the neighboring plants yet. Okay, so basically the exercise is to fill in these steps in this skeleton code. If you have any questions just go ahead either in the HackMD or in the notes and let us know by using the green check mark when you're done down here. Okay, so let's start. So yeah, if you're working in a different notebook or in a file it's important to get all of these. Well, I guess not these ones necessarily but at least these two definitions. And then we can actually get started with the simulation. So the first thing is basically we only do something if the cell is infected. Otherwise with true interactions they may get infected but they don't really change at all in simulation unless they get infected. So, well first in the parameters of a plant, a recovery time and a death rate. So if the plant.status, so the plant has a status. If the plant.status is equal to infected then we do something. And what we do is one it ties with the probability of the death rate that keeps happening. So this is already here like I already explained this is just a random number between one and zero. Yes, around one. So the probability of a random number between zero and one being smaller than the death rate is the death rate. So this happens at the right probability. And we'll also use the same construction for the interaction when the cells have a random chance of infecting each other. Luca, by the way, I haven't found a nicer way of getting a single random number, but do you know one? Yeah, just a random, without any input should generate a random number. Oh, just one. Yeah, I mean a random, just parenthesis without any, yeah, like that. Okay, okay. So if I have run one, then it gives a least length one. Yeah, and if you give like two inputs, it would be a matrix. I wonder why I didn't spot this in the instructions. Okay, so actually this one would be much cleaner this way. Okay, thanks. So if the cell dies, a plant, if the plant dies, we do the advice changing its status. And since it's mutable, we can assign directly to the status. So we call it not infected, but dead. Okay, now we always add one to the infection time when we take a time step. So plant dot infect, is it infection? So the tab key, I think will complete this, it's infected now, no, it didn't complete it properly. Infection underscore time, I think. Let's just go up and see infection underscore time. Okay, here we are. So, yeah, there is, somebody asked if Julia has this add assignment of this, modifying assignment operators and it does, so plus equals one will add one to the infection time and assign it. And then the last thing is, if the infection time is greater than the maximum or the recovery time, then the cell becomes recovered. So we have if plant dot infection underscore time is larger than, and now it's called recovery underscore time here. Then plant dot status equals recovered. So there's a bit of repetition here, which is nice. And a bit of typing. So, but yeah, if statements and the dots to get the contents of a variable, of a struct. Okay, now this should work with a plant. So we have defined plant one and plant two. Plant one is uninfected and plant two is infected. So what should happen to plant, let's call update for plant one. So what should happen to a plant one is it should definitely, oh, you can't call it just one parameter, it has three parameters. So recovery time, let's say five. So it takes five time steps for a plant to recover and death rate, like 2% is sensible for the simulation. Depends of course on what the infection rate is and so on, how big one time step is. So now it should work, but it shouldn't really do anything. We actually have to print plant one to see if anything's happened. Plant one is infected, oh, plant one, plant two. Oh, I changed it into infected here, sorry. Let's rerun this. So plant one is uninfected now. Okay, and it stays uninfected no matter how many time steps you take, as long as it's not interacting with anything. Plant two is infected and it has infection time one. Sorry, infection time zero first and then it increases by one every time step. It might become dead at any point, let's see. But if it survives five time steps, then it should become recovered. Two, three, four, five. Okay, now it's recovered. Let's go back and start from zero and increase the infection, so the death rate to 0.5. Okay, it's immediately dead, all right. There's a hand up, but that's probably from from coming back from the break. But go ahead and ask if you have a question. Okay. All right, now this could just as well have been an exercise, it's very similar, but here we have a function that takes two plants instead of just one, maybe. So another thing that's kind of new here. So here we just have plant and in principle, this plant could be any type, but it needs to have a status and an infection time. If it has those two things, then this function will go through without the problem. Here we specify that it needs to be a plant using this double colon syntax. It's an interaction between two plants. So it also gets another plant as a parameter. There's this plant and this plant and other plant because we will only be modifying this plant. So the other plant may infect this plant, but the interaction only goes one way in this function. And later you'll see why. I will point it out in case, because I don't think, I'm not sure it's written down, sorry, but I will try to remember to point it out. Okay, and then it gets an infection rate which is the chance of one plant infecting another. I keep saying cells, sorry, because I used to call themselves in the previous version of this course. So, okay. So what should happen is if this plant is uninfected, that's uninfected is the only status that can get infected. So that doesn't get infected, infected doesn't get infected, immune doesn't get infected, and recovered also doesn't get infected. We could make it more complicated by adding and reinfection rate, but that's not there in our simulation. Okay, so it needs to be uninfected and the other plant needs to be infected and then something might happen. That's the only situation where any interaction between the two plants happens. Okay, so there's that if statement. And then there's a random chance of this plant getting infected. So again, we just take a random number, see if it's smaller than the infection rate and that should give us the right probability, okay? And then what happens if the plant gets infected? We change the status to infected and we put the infection time to zero, okay? I should have thought of this earlier, but I mean, I should have written this in a different notebook. I'm not going to go back and read and write the whole thing down, but yeah, it's much clearer if I write it rather than just point things out in the code itself. So sorry about that. There's no greater new cell here. So the interact function should now take two plants and if one is infected and the other one is uninfected, there should be a chance of infecting the first one. So we'll create plant one. And this is a plant that is in the state uninfected at first. And the number doesn't really matter that much. The infection time number because it is not infected. Now we'll also create a plant two. Like earlier, this one is infected. Let's say it's been infected for two times there. I mean, it doesn't really matter here, but yeah, let's make it realistic. Okay, then we'll call the interact function with plant one, plant two. So plant two now may infect plant one. And let's give it a 50% probability of infecting. And print, well, we don't need to print statement necessarily just have plant one here. Okay, so first try, it did get infected. Now we can rerun it multiple times. It will always start from the same initial state because it just runs this here. So it just recreated. But about half the time it gets infected and half the time it doesn't. Okay, so it seems to work. Okay, and then we'll get to a somewhat different section, multiple dispatch. So I'll hand it over to Luca. Yes, perfect. Sure. And actually I'm answering now a question which we are going to talk about, about multiple dispatches. So that's perfect moment. So whoever asked that question, how come do you are going to have a more extensive answer soon? Okay, so boom, boom, boom. I'll just a second. I'll start sharing my screen in a second.