 Now on how to do contract based programming and how to use contracts to make your programs easy to see. Yes, and contract based programming, you can discuss a lot exactly what it is. One view is that it's about static analysis and statically proving that your program is never going to fail. We have a talk after this one about Spark and how to do that on the basis of ADA. I take this slightly lighter approach that it's basically like doing preventive debugging. Insert lots of the assertions everywhere and have them checked by the compiler to ensure that your program state is actually always what you expect it to be. Well now we all know, at least those of you have tried, that writing assertions, especially if you have to put them everywhere sensibly, it's a lot of work. But here ADA actually helps us quite a lot. It has many features for inserting assertions. So you can write them once and actually have them in the code many places. We start with the type system that an integer is not just some machine integer, it's actually an integer with a specific range. That's a very simple assertion if you ask an ADA programmer, but it is an assertion still. And it's something you don't have to write it every time you change your variable when you're programming it in ADA. Because the compiler puts the checks in whenever there's a risk that you might break the contract. The check may actually be something as simple as a interrupt handled by the CPU. Or it may be an explicit comparison, but you don't have to worry about that as a programmer. You just say what is the range. Another important thing about assertions is that it's comments just much, much, much better. Because assertions they are understood by the compiler, so it's something about the program state and how the program works. That both the read of the source code and the compiler understands so that you get much more use out of it. I would like to say that the ideal software has zero comments. I'm not that good a programmer myself that I can quite get there. But I figured that out some years ago when the Software Engineering Institute announced a model of quality of software could re-engineer from the model that the best quality software that had 27.3% comments. And that couldn't possibly be true if you asked me. So no, zero comments is the best quality software. Of course in reality we're not that good programmers so we have to put some in. But no, use assertions instead of comments whenever you can do it. Then the compiler understands what you're doing as well. Another thing about assertions, there's a tendency even in critical projects that assertions is something you have one time enabled only during testing but not when you actually deploy your system in production. But don't, when your system is in production you really want it to be in a sensible state. If it's not in a sensible state, it's doing random stuff. And then you better shut it down quickly. My previous software development project I was working on warehouse control and management. And if you have a 27 meter tall crane just running amok, you don't want that. You want it to stop safely if it's not in the state that you expect it to be in. So no, don't disable assertions just because you're putting code in production. If you're something you have a critical timing and you have to remove it because otherwise you can't meet your timing constraints, then prove that the assertion is true. Don't just disable it. Prove that it's true. And if you can prove it's true, then you don't have to check it at runtime. And then it's okay to disable that assertion but only that one. And that's where one option at least is to use Spark or you can also do the proofs manually. But the safe thing is to have a tool that does it. Okay, the typical view of programming contracts. That is that they are about pre- and post-conditions on sub-programs. Here it's a procedure. It could be a function as well. It could even be entries and protected objects. So you have some conditions on the parameters when you enter the sub-program and some promises about the parameters when you leave the sub-program. It's very good and important. But in my view, it's not really the core of contract-based programming because, well, we can do it anyway just like this. This is the same code. I just moved the assertions inside the body instead of having it in the specification. So, well, okay, we do get some benefits from having them in the specification. We make them visible to the users of our sub-program. We don't want other users of our libraries to read the insights. They just need to read the specifications. Okay, that's what it does. And then, okay, I use that one. And then we want to write how our program should behave or sub-programs should behave when we write the specification of them. We don't want to write that later. So if we put it in the specification, it's easy to remember you've write it there. You don't put it in a node. Remember to put it in the body when you write that later. And then, for some static analysis tools, putting them in the specification makes it clear that it's part, that those contracts are part of the interface with the surroundings, which means that you have a clearly defined boundary. So for the tools especially, it's very good. But in reality, you could do it all with old-fashioned assertions. And, well, it's not much more writing like this than like this. So you don't save so much work as a human, as a developer. You make life easier for your users, which is always good and healthy. Usually, you'll end up being your own user at some point. But if you, on the other hand, put contracts on your types, then you just put the contract in once where you declare the type. And then whenever you use an object of that type and you modify it, then you have an assertion that the compiler will either prove to itself that, well, it's true or check it for you. So there, you write it once and you have it hundreds of places in your code. So that's what I call scaling of your work. So you get the big benefits when you put your contracts on your types and you get some smaller benefits when you put them on your sub-programs. There are, of course, all these issues about compile time and runtime checking. Good data compilers, they prove that most, they'll typically prove all these assertions about the types at compile time and then not actually put in a runtime check unless they can't prove them. And if they can't prove them, maybe it's because you have an error, but you don't look at that because you find that interesting. Here, the very simplest kind of type contract in ADA, that's a range. So here, these two declarations are actually from the standard library. We have natural numbers and positive numbers, well, except they have an upper limit. We can also declare ranges of other numerical types than integers here. I've done it on the floating point type. You can do it on fixed point types. You can do it on enumerations. Oh, it looks like this is... I've worked in Mauritius for a while for a small software engineering company there. So some of my notes, they are from the Southern Hemisphere. There are also slides from the Northern Hemisphere. I'm having fun with the seasons occasionally. The next step up from ranges, that's called a static predicate. In Mauritius, they say it's summer from November to April, but we don't have wrap-around on enumeration types. So I have to say that that means it's from November to December or from January to April. We'll just go on. Up from that, we go from static predicates to dynamic predicates. With dynamic predicates, any logical expression, any Boolean expression can be put in as a condition. Here, I have a subtype. You can only put primes into variables of this type. You might complain about computational efficiency, but the logic is sound. Another example, check. Sometimes that's only for times which are in the past. That's okay. It's ever-growing set. This one is naughty. The last hour. You should never have variables which I actually have this type because the value may be valid now, but in an hour, it's not valid anymore. So this is more of an example that I came up with to show the possibilities. You can also see that these look like I'm declaring ranges, but actually the problem is that this type here is not an numerical type or an enumeration type, so I can't use the range construct directly. So I have to fake it in this way here. Well, using clock doesn't help, of course. And then equivalent to dynamic predicates, we have the concept of type invariance. They are just like dynamic predicates, except that they are for private types. And there are a few special cases about exactly where the compiler checks for them. But basically, if you have a type where the public view is just that there are some data inside. You don't need to care about what it is. Then you can't put much of a constraint on the outside that makes sense. So if you need a constraint that relates to the internal data structure, then you have to put it on the private side and then it's called a time invariant. Here, I say, okay, it coordinates, but they have to be inside the unit disk. And this is something I've adapted from the 8 of 2012 rationale, which is the explanation about the language and the changes to it. One thing, actually, before we go to this one here, is we have the ordering of this complexity of how we specify the contracts on our types. And I'd say always try to use a simple way to describe the contracts as possible. That gives you more possibilities of how to use it. Did I skip the slide? Apparently, I did. Okay, never. But then now that we've talked a bit in general about the concepts of the contracts and how you've seen how you can write them, then I'll go to actually guidelines if you should sit down and start to put in contracts doing it data style. I had some experience myself when I started using data 2012 parts of contract-based programming that I didn't make it consistently. So these are some guidelines to try to use these techniques in a consistent manner. First part is simply that first work on your types. Make sure that you have specified your types in a good way. I think that's always sensible, but it becomes even more sensible when you try to put good contracts in your software. Then you start specifying your sub-programs. And again, there you need to put good contracts on them. I didn't spend so much time yet talking about contracts and some programs and come to it. And then finally, we use packages in Ada or you can say libraries. It doesn't matter so much. But the point is that when you have a collection of sub-programs which are supposed to work together, then it's sensible that the contract of the different sub-programs actually match up. So if you have some use case where you call this function first, then this, and then this, and that should work, then the contracts should also match so that they actually say that what comes out of this is valid for continuing to call this one next and calling this one next. So we'll go through that. Well, type declarations as detailed as possible. First choice in Ada is do you actually declare a new type or do you declare a sub-type that is a subset of an existing type? The difference in Ada is that sub-types, they can be copied between each other and between to the objects of the parent type without any explicit conversions. But if you have a new type, you have two different types, not just different sub-types, then you have to do conversions explicitly. Either you can just, in some cases, you can just convert using the name of the target type. In other cases, you have to actually write a function to do the mapping for you. So that's the first consideration. And it's not always obvious. Sometimes you try one thing and then later you do something else. One experience I have there is about half a year ago working for a customer. They had used sub-types of strings for handling address codes in a warehouse. That's all good and fine and there were conversion functions to convert between the different kinds of addresses. One kind of cranes used one format, another kind of cranes used another format, and the warehouse management software used a third format. But then we found out that two of these address formats had the same length and some programmer had accidentally converted from one format to another and then from one format to another. But then, okay, we switched to, instead of having sub-types, just having subsets you could copy freely, to actually use new types. So it was still strings, everything, but it was several types. And then the compiler said, hey, you're cheating there, you can't do that, and then we found five places in the code where somebody had actually messed up and were pushing incorrect addresses around in the system. So sub-types, just using sub-types can be dangerous, especially if they have significantly different meaning. And strings are the most dangerous types of all. I think integers are not that dangerous, but strings, they can really have different meanings. And so, yeah, I'm scared whenever people use strings, even when I do it myself. But then the next step is to put, if it's appropriate, put constraints on the range. That only makes sense for numerical types and numerations. And then we start looking into whatever extra constraints might be necessary. Primes, we can start out saying, okay, it's integers, they're larger than one. And then beyond that, we can't do it with the ranges. The rest we have to use predicates for the extra constraints. Yeah, here we have primes, they're integers. They're larger than one. More specifically, go from two. And then we can't handle larger than integer last. And no other factors than one and then prime itself. Those of us who are already AIDA programmers pride ourselves that AIDA is readable. But I would like to see, for non-AIDA programmers, is this correct? Does this look correct? Seems correct to me, yeah. So it's for all n's in the range from two up to prime, the value we're looking at minus one. Then prime modulus n has to be different from zero. So I like the notation here for these expressions. It's sensible. Then I talked about that you have to use a simple types of contracts as possible. One of the reasons for that is that the complexity of the contract puts limits on how you can use it. If you want to declare an array, it has to be a discrete indexing type. Continuous indexing would be a bit of a mess with an array. And you can only have constraints in the form of a range. So here, again, we see from the standard library, subtype positive. And then actually also from the standard library, the basic string type in the standard library, is an array of characters, positive indexing, but unspecified specific range. So you can both declare strings, specific string index from two to four, or one going from one to ten thousand based on this type. But the important part here is that if we put this constraint in as a static predicate or dynamic predicate, then we would not be allowed to use positive here because the contract was too complex. The problem is that if you have a static predicate or dynamic predicate, you could have a discontinuous set of values here and telling the compiler to handle an array with holes in the indexing. It was decided that was to make too much trouble for compiler writers. Then another boundary is between static predicate and dynamic predicate, case statements in Ada. There you have to ensure that you cover exactly all the possible values of the argument. Nothing more and nothing less. And static types declared with ranges and static predicates, that's okay. So here now we've gone up to the northern hemisphere. So spring is March to May and winter is December, January and February. You can feel it outside. Then we can have a case statement like this here where we have the base type month. So we have input here is of type month. And then we have a case statement that covers each season. So printing out different messages depending on the season. And here you can see we cover exactly all the possible seasons. So there was some overlap, if I put in an overlap between some of the seasons saying that, well, winter sometimes continues into March. Then the compiler would complain because is it March, is it spring or winter? You have to decide. But the static predicates, that's the limit there. Dynamic predicates, then it can't be checked at compile time if you have exact coverage of the whole set of values. By the way, what? If you try to cover the seasons on both the northern and southern hemisphere, is there an elegant way to say, oh, the seasons on the southern hemisphere are the same for just half a year away from this? Oh, first of all, they would disagree with you on my ratios. They say they only have summer and winter. That would be difficult. I wouldn't do it like this at least. I'm not sure exactly how I would do it. But I don't think I would do it with a type system just like this. I would say you could also say this is toy examples in a way. In reality, I'm more used to this being different kinds of communication protocols and things like that. Seasons are a nice example that all of us understand except that we disagree about the details. So no, where was I? So that was it, yes. I have a whole talk just about case coverage, which is online as a video, you can see. And also, I wanted to say, you don't have to type right down all the examples. They are online. There are links in the slides, links on the FOSTA website. So you can download them later on. Okay, then now we say we've got our types in order. We've got the details sort of in order. Then sub-programs, functions, procedures, entries, whatever we call them, operations, methods. Of all, okay, which arguments do they have? Formal parameters, we say neither. And then what is the direction of the formal parameters? Is the data going into the operation, data coming out of the operation, or actually data going both ways? Then we select subtypes for each argument. And then finally we can put in specialized pre and post conditions to describe relations between the arguments. And any constraints we can't be done just with the type system. A nice, classical example. Sub-program here for incrementing a counter. Here I've actually gotten ahead of myself. I've both decided, well, they are integers of some kind. The counter comes in, is changed, comes out again. A step comes in. I did consider if I should put a default value on it. In Ada you can actually put in a default value declared here. And then if you call the sub-program only with the one parameter, then the second one is just passed in as a default value. But never mind. Okay, but no, we don't want our pounders to be negative. So we use natural instead of integer. And having a zero negative step, well, you could just argue for negative, but zero steps don't make sense. I decided to go for only positive steps. So that's putting in the subtypes. Do we then have any additional constraints? Well, counter should be less than the very last possible value because otherwise I can't increment it. So this is one precondition. And then actually another precondition could be that the step should be match, should be small than or equal to the difference between the last possible value and the current value of counter. So one more. And then when we're done, the counter must be larger than zero because we've incremented it and it started being zero or more. So this is an example. You could also put in a post-condition that basically finishes writing the sub-program. That counter in the post-condition is counter old plus step. And then you're done writing your sub-program. Well, you still have to implement it, but it's getting very boring. But basically for very simple sub-programs and functions, you can put the whole expression of what it does in the contract. And when you can do that, it sort of demonstrates that it's a toy example. But having done that, we may still need to refine the specification. Some questions I usually say you should ask. Do you have some sub-programs with special requirements? Some requirements that should be met before you call them. A classical one is that you have a library where you need to call some initialization sub-program before you can actually use it. Do you have a sub-program that should only be called once, like the initialization function for said library? Or can the sub-program only be called when the system in some other way is a special state? Or in a way they're variants of the same question. But still, here I took my sub-program from the standard library. In the standard library we don't have this part. But writing to a file, I think it's very good if it's open, if it's writable. So the mode is either you write to it or pen to it. So that's one example of something you would add. Besides, you could of course discuss if the real solution here wasn't to make this a subtype, a file type. And then just say all the write operations have this writable file subtype as argument. And then we suddenly figure out that maybe we should declare new subtype here, put that in instead. Or we just use this precondition. Another one, this is from the example with a library. You have to initialize, but it's only sensible to do it if the library is not initialized yet. Well, and then now you just get a bit of opinion. I'm not sure how sensible it actually is. But the ideal pre and post conditions in my view should be something as simple as the name of one of the formal parameters is in some subtype. I have examples where, even in these slides where it's not possible. But I would like to be there. So that was a bit about putting contracts on the subprograms. Then another step, that is when you have a whole library, a collection of subprograms. It might be the standard library IO submodule, text IO submodule. And we want to be able to use the whole library in a sensible way. And the contracts should be matched up between the different subprograms there. And trying to write down some guidelines for that part as well. Basically, we want the post condition of one call to match the preconditions of the following call. So we look at our, first of all, we look at which use cases do we have for this library? Which sequences of function calls do we find plausible or did we intend with this library? And then we take one of these use cases and go through the calls. And then, first step, we verify that the documented state of the program when you get there matches the constraints and the preconditions on the first subprogram there that we call. And if there's a mismatch, we go back and fix it. And then we document, okay, what is the state when we leave this subprogram so we can continue onwards to the next one in the use case and see if they match up. So let's try it. We look at a subset of the standard package eta.txt IO. It has a procedure for opening a file, one for closing a file, put line for writing a line of text out to a file. Here I've added some pre and post conditions already. This one was the same as for put. And then additionally, I say that the line number of the file is incremented by one after the call. So I refer to the old line number of the file and compare that with the current line number of the file that has to be added one here. Anyway, we just look at a very small part of it here. And a plausible use case. Open the file, put a line, close the file. Since open didn't have any preconditions, it's very easy. Everything's okay. We could say that maybe I've forgotten something and should have written that in a file that's already open. But who knows? We may also say that if the file is open, we just close it first, whatever. So there's no mismatch. And then afterwards, target was an out parameter. So we know open has modified target in some way, or may have modified target in some way. So we don't know anything about target except that it has a valid value of file type, whatever is valid for that type. So then we go on to the next one, to put line. Here we have a problem because put line requires our file to be open and writeable. Okay, so we have to go back and modify open if we believe that that's correct to say, okay, when we leave open, we promise that the file actually is open and the mode of the file is the mode that was passed in. Then since we go back open it with out file and out file is one of the valid states for put line, then now we'll actually, after having called open, our data will be in a proper state for calling put line. So we have corrected now to match our use case. We didn't match either there's something wrong with our use case or there's something wrong with our contracts. And also if we make the contracts nice like this, the compiler can say, okay, he calls this one, and then the post condition for this one matches the precondition for this one. So I don't need to check and entering the second one because we already knew check that once. So we save more checks if we have well matched pre and post conditions. So more efficient code. Wouldn't we have to check whether there is enough space left on the device for the new line? Well, there are certain issues, especially taking something as tough as I owe. So we, in reality, yes, we should check if there was space on the device and other things. Also, we're not at all looking into the issues related to, can we actually open a file with this name? This is not perfect, no. I think it's a bit more than just a toy example because it's actually a library we use, or you might use if you're a programming adder. Anyway, it would not be atomic. No. So it can change between the time you test it and the time you need it. So it's impossible. So it's one of the cases where you just have to accept that you get an exception there anyway. Exactly. But we continue and close. It has no preconditions. So in theory, well, no problem. But we implicit mismatch that we know we come with target open. So maybe we should put in a precondition and close saying that you can only close an open file. Or maybe not. That's sort of more of a decision about how does the library work. Because a stricter constraint going to a less strict one, that's not a problem. So there it's a matter of design issues. But afterwards we know that target has been changed and can have any valid file time. There we might want to document that it's actually closed. But in general, you can see this is just, it's very partial and it's a good exercise to look into. Actually, I think the ARG, are they working on putting contracts on some part of the standard library? Yes, I think we have to add more contracts. So it's a soft, we should add more contracts. I don't remember. Okay, no problem. So I'll give you the very short version of my talk. Don't disable unproven assertions. And using the type system is possible to write assertions centrally and have the compiler insert them everywhere they need to be. So it's a way of writing assertions which scales very well. Little work, lots of effects. Then don't use more advanced contracts than you need to. And the last one we've been talking about, use cases for your packages to check if your contracts are complete and consistent. And link to the examples. The link is also with the slides in the custom website. Yes, any questions? So if you make a subtype with your contracts, can you end up with very long-named subtypes? Well, I could name my subtype I. I wouldn't do it because I'm me. Actually, the last place I worked, we put in a rule that all our identifiers had to be proper American English. American English, I didn't like it, but the ADA standard has identifiers in American English, so mixing British and American English is a mess. So, no, you could call the subtypes I, J and K, but you wouldn't do it, no. What I meant was I'd imagine me calling the type after the conditions of which they're checking. So far, which is open-hand this and this and this. No. You choose, it's a balance there, but you definitely get longer type names than int and float. But nowadays, you have widescreen laptops, so the 80-character limit on source code. I enforced it on my own code until two years ago, but now I've decided, ah, the hell. You can list the subtypes in the package. The type name can be just a pink. The package name is really the type of the name of the subtypes. So it's like a name space. Yes, yeah. It can help a lot. Yeah. No names are not really an issue because we have good editors with automatic completion. Well, you're sticking... You name it the first time you give a readable name. Well, then you just hit tab and it's written. Every time you use it, you don't have to have to... No, but it's still... It's not an excuse for an readable name. No. But sometimes you may end up hitting the edge of your display. And then you have to think a bit more about your naming. Yes, definitely. So I know you said you should try and use a simple contract or post-condition. Yeah. If you were... let's say you had to use a dynamic predicate. Is it possible to write predicates that never finish? Yes. And in practice, is that a problem? Sorry. The question is, is it possible to write a contract, a predicate check that never finishes? And easily I can do that. And I can't remember if I've done it yet. But I've heard of people accidentally putting in some recursive calls in their contracts. So they checked something... Can that have resolved the problem with the recursion now? So the standard and the GCC ADA compiler has solved it. But I think I've heard somebody that I accidentally stepped on that one with recursive contracts. So... But it only runs at runtime, not... No, the compiler is not required to check any of this compile time. Can that check static predicates? Can that check at compile time? Well, it can do it anyway for case statements. Whenever it can see that you're doing something that will raise a constrained error at runtime, you get a warning. It's not allowed to fail the compilation. Because that would be breaking the standard. But you get a warning, you'll get a constrained error at runtime here. A function call is not allowed in a static predicate? Yes. The static predicates are simple set operations. The function call is not static. Yes. You talk about static predicates. Yes. But my view point contract is fully dynamic. It's at runtime. Yes. My view point static predicates are more close to dependent typing or something like that. Well, the question is about the static predicates, and that they are static and implicitly compile time, and our search and contract-based programming also... We're talking about runtime checks, but they're still... The staticness of static predicates is in the expression of the static predicates. You may still have that the static predicate is a subset of another type. So when you do a runtime conversion, it may or may not be valid. So like if you make a... Yeah, but we could take one of the examples with the seasons here. If we have some month, one object of type month, another of type winter, then converting from month to winter, we have to do a runtime check if it's valid. Even if this expression is static, it's a compile time expression. We still have to do the check at runtime when we convert from another type. So in that sense, yes, you still have runtime checks. Just like with ranges, if you do an addition of two integers, you may exceed the range of the type that you're putting them in. So there are still runtime checks even if the expressions are static. Does that answer your question? Yeah, just very much. Okay, yes.