 to a dialogue limited and today I'm going to talk about tests, derivations, and proofs. Somehow my update to the conference website on the title of the talk has not been put up, so this is the correct proofs, derivations, and tests. John Hughes and Mary Sheeran in their talk, Why Functional Programming Matters, listed four salient features of functional programming. And similarly, Ken Iverson, the inventor of APL in his Turing lecture in 1980, notation as a true of thought, listed five important characteristics of notation. We will illustrate these points using the language APL by showing some test techniques, deriving program simplifications, and proving the correctness of some programs. These techniques are in daily use in real world applications at dialogue. Assert APL utility function we use in the dialogue QA suite. And the details I won't, the subtleties I won't explain. So as I said, the main thing about assert, this assert utility is that the conditions that I want to be true are stated in a positive sense. So for example, CO equals 1 plus e to the pi times COJ1. So we'll illustrate the use of this utility on the Diamond Cata. Cata is a programming exercise used in the agile development, extreme programming, and test driven development communities. The problem description here is by Ron Jeffries of TDD fame. Notice his use of the technical term whatever. But I mean realistically, that's quite often how we receive specifications. It's stated like that. Okay, so the input would be a bunch of letters, and you're supposed to generate this diamond shape output. So here is the solution in APL. I can't claim credit for deriving this, Morton wrote this. Remember that? I won't try to explain the details here. The details are in the dialogue blog. Oh, by the way, the presentation will have a lot of very fine details. Don't worry if you can't copy them all down, because all the slides will be in the conference website. So I won't explain the details of this function. But what we're going to do is to talk about testing solutions to the Diamond Cata. The APL function treats the required construct as a matrix rather than as individual lines or even individual letters. This is the whole value idea of Hughes and Sharon. Such a way of looking at the problem has its benefits as we now demonstrate. So DC is an operator or higher order function which takes a function operand named alpha, alpha, whose argument, omega, are the input letters. And then it computes a result, which we name Z, and we're going to apply a bunch of assertions on the result. Ideally, the assertions would fully specify what a Diamond Cata function does. That is, a function is a solution to the Diamond Cata and only if all the assertions are satisfied. Another way of saying that is that the assertions should be a set of deserterata which leaves nothing more to be desired. I'll go through the assertions one by one. Now n is the number of input letters, so the solution Z should be a matrix of shape minus 1 plus 2 times n unless you input zero letters in which case the matrix should be zero zero. And next, the solution should be symmetric in the vertical axis. So if you flip it left to right, it should remain the same. And similarly, the solution should be symmetric in the vertical axis, like if you flip it from top to bottom, it should be the same. And I'm going to form Q, which is the upper right quadrant of the result matrix. And the diagonal in this upper right quadrant should be the input letters. And finally, in this quadrant, the off-diagonal entry should be the dash. So these matrices in the boxes would fail to be a solution to the Diamond Cata problem on each of the assertions. So this one is not a matrix of, you see, what's 4? Yeah, the shape would be wrong because if n is 4, it should be a 7 by 7. But here it's 8 by 7. Yeah, here it's not symmetric in the vertical axis because this is there. And here it's not symmetric in the horizontal axis. Like if you flip this, it will be a different matrix. And here the upper right quadrant would not be the input letters which are A, B, C, and D. And here in the upper right quadrant, the off-diagonal entries are not all dashes. So you can see from the example of the Diamond Cata, the tests are often descriptive rather than prescriptive, asserting what a result should be rather than how to compute it. And for that reason, they tend to be more robust and easier to write than the actual function. For example, it's much easier to test that R is a root of a polynomial than to derive the root in the first place. And another example is that it's much easier to test that S is a solution to NP-complete problem than to solve the NP-complete problem. And lastly, if you can afford to have multiple development teams, one of the teams can be devoted to writing tests. Now I'm going to now relate a recent experience where testing has had quite a beneficial effect on the long-standing problem. I set out to write tests on the transpose primitive in APL indicated by the symbol. So I worked on the right argument, in this case, the matrix Y. So the right argument can be in the array and the left argument is a vector indicating the order that you take the axis in. So for example, one zero transpose of Y means first you take axis one and then you take axis zero. And zero one transpose Y means you first take axis zero and then take axis one. In this case, it would be the identity function. And finally, if you have repeated elements in the left argument, you get diagonal section. Or in other words, the axes are run together. Another way to look at transpose is that the left argument, in this case X, specifies the order in which you, let me get this correct, specify the order of the indices or subscripts that you need to obtain an item of the result. So for example, I have this Y in this case is a five dimensional array of random numbers. And I'm going to do this kind of transpose two one two zero one. The result happens to have ranks three or it's a three dimensional result which you can index by i, j and k. And to get that element, you take the indices in the order specified by the left argument. So if i is zero, j is one and k is two, then two one two zero one means you take k, j, k, i, j. Another way to say the same thing without using the semicolons so that you can have expressions that work on the arrays of any rank would be, you know, you index said by the enclose of V, that should be the same as Y indexed by V's indexed by X. Now, this second way of looking at transpose requires that you know the shape of the result, which from the description, you can derive the following computation for the shape of the result. You index, sorry, you group the shape of Y, which is row of Y by sorted X. So you do zero first, zero gets three from the shape and one has seven and two and then for two, you get eleven and five. So this is grouping the shape of Y by sorted X and then the shape of the result is the minimum in each group. So for zero, the minimum is three because that's the only thing that has and for one it should be two and for two it should be five. So lo and behold, the shape of X transpose Y is three, two, five. So this is a computation for the shape of the result. It also means that the rank of the result is one plus the maximum of X. So the maximum of X is two and the rank of the result is one plus that, which is three. So this leads to the following tests on transpose. Again, Tc is an operator. It takes an operand function denoted by alpha, alpha and purported to be in transpose function and then we apply assertions on the argument while we're at it and then also assertions on the result. So the assertions on the arguments we call preconditions and the assertions on the result we call post conditions. So let's go through these conditions one by one. So this says that the left argument alpha should be a vector and the length of the vector should be the same as the rank of the right argument. The left argument should not be negative. It should be greater than or equal to zero and it should match the floor of it, which means it should be integers. So we take the maximum of the left argument, but at least zero. At one to it, that should be the rank of the result and if you compute the integers from zero to r minus one, alpha and i order r should be mutually inclusive. So for our example, r is three. So i order r is zero, one, two and alpha in our example was two, one, two, two and two, zero, one. So they should be mutually inclusive, alpha, epsilon, i order r and vice versa. Now the post conditions, the rank of the result should be r. The shape of the result is what we computed on the previous slide and then a random element of random item of z should be the argument indexed by those same indices ordered by alpha. Okay? And then when we run the example, it is true. None of the assertions fail. In this case, the assertions fully specify the result. This last assertion the result is on the random element, a random item of the result, which means that it fully specifies the result because it is true on the random item, it's true on all the items. Right? I mean, if you run the test repeatedly and each item has a non-zero probability of being picked, then if, you know, then you have fully specified the result. So from that, I picked a random index from according to the shape of z. You know, if you index by those indices and compare it to the arguments, select by those indices ordered by x, it's true and we rewrite the indexing function using a functional form of indexing so that we can apply it more easily to multiple indices. So z itself is exactly this expression. So from that, we derive a simpler, a very terse model of transpose. We are just substituting or converting this expression into a lambda computation and substituting the expression we had for the shape of the result. Work on transpose has a long history in APL. The result shape was done in 1979 and the definition of transpose itself in 1987 and as recently as 2016. The current definition is superior to the earlier ones and it derived by meditations on testing and by realizing that true on a random specimen means true for all. Now we're going to do some proofs. Now we prove an efficient computation for the sum of the integers from 0 to n minus n. So iota n are the integers from 0 to n minus 1 and the way we do proofs here is that an expression is equivalent to the expression immediately preceding it for the reason stated in the annotation. So you can go from one step to the next and it's true by the annotations if there's one. If it's too obvious, there may not be an annotation. According to legend, Gauss knew this when he was a schoolboy. Sorry? Ah yes. Yeah. Okay. Yeah. Which brings up is a good intro to the next slide because Gauss knew says that beware of bugs in the above code. I've only proven it correct and not tried it. So obviously I've not tried the annotation on the previous slide but here because all the expressions are executable, you can type them into the computer and check that at least they give the same result and then void embarrassment such as what Morton just pointed out. So that's pretty easy. How about something a little bit more complicated? So here's we're going to invert non-singular triangular matrix which is a triangular square matrix. So we have a recursive algorithm for it here and it's derived by inverting a two by two matrix of numbers. Pretty easy. But when we do the inversion of the two by two matrix of numbers, we avoid depending on the community of modification. So now the next step is to use the code that you have for inverting a two by two matrix of numbers to invert a two by two conceptual two by two matrix of matrices substituting matrix multiplication for scalar multiplication. So x is this plus dot times which is the matrix multiplication in APL. This by the way is a way to invert a triangular matrix in parallel because you see it's you can so here's the matrix A, B, zero and D. You can invert A and D separately and then combine them together with B to get a to get a inverse of the original matrix in parallel. Anyway to prove that this algorithm is correct we need to verify that the four block matrix equations are correct. The first one this yeah so because it's a two by two matrix of matrices there will be when you do the in a product there will be four possible entries but they're block matrices so you can verify that these steps are correct. The most complicated one is this let's see C the one that says C below A, B times BX, A, B in the product of BX DI and you can satisfy yourself that it does give zero. Now even more complicated version of the technique you know I'm given a square matrix M which is Hermitian and positive definite and the Kuleski decomposition is to find a lower triangular matrix L such that L in a product with the conjugate transpose of L gives you the original matrix. So L is the kind of square root of the original matrix. This is not special notation transpose is the thing we were looking at before and monadic plus in APL is conjugate. So as for inverting a triangular matrix we can do this two by two technique so this is the function we're applying to the original matrix. We divide it into a conceptual two by two matrix of block matrices and because the matrix is Hermitian that means this corner is the conjugate transpose of the upper right corner and then we're saying that the algorithm summarizing this diagram is you know the zero zero block is recursively the function applied to the to the upper left corner. This is zero and this is T times our matrix product with L zero and this is another recursive call where T is this matrix formed by the conjugate transpose of B times the inverse of A matrix inverse of A. So to verify that the algorithm is correct we need to check these four block matrix equations and also that L or the overall result matrix is lower triangular. Okay first one is easy because it's true by induction or recursion. Next one is the symbols maybe a bit foreign but it's straightforward applications of matrix algebra and the definitions of the APL primitives. Again each of these lines are executable APL expressions so you can enter them into the computer to at least check that the culture is oh this is an easy one because you get equation C by applying conjugate transpose to both sides and then finally a bit more equation he's I've been I mean longer steps in the proof but again this is straightforward applications of matrix algebra. Finally all four equations are checked out we just need to convince ourselves that L is lower triangular which it is by induction. Now if this proof feels like a proof in math it's because APL is executable math notation. Okay next we're going to look at famous function Ackerman's function famous in computability theory it has a simple definition in APL and I think it's also simple in other languages as well. Again the left argument is alpha and the right argument is omega and the del the symbol indicates recursion. Ack is a very fast growing function which you can see by fixing the left argument and then applying to a bunch of right arguments which you can do by using the each operator which is like map I believe in other languages so I'm using zero except I'm mapping a dietic function right so is it a slight generalization of map because I'm doing dietic functions yeah so it's applying each corresponding pairs of arguments and when there's only one thing on the left then it's applied to every element on the right so as you can see when the left argument increases the function grows more faster and faster you notice I very cleverly didn't apply four Ack each to Iowa 16th because it would still be running it would still be running when the universe has turned to dust and the dust is gone so what's the pattern well if you stare at it for a while you can see the pattern and the pattern is this if if you have some function f we don't know what yet but if you have such a function f and Ack omega is f under three plus on omega I'll explain all these in a bit then if you increase the left argument by one that is alpha plus one Ack omega then it's the same function power one plus omega under three with plus on one okay so this is a very useful operator under or dual operator where f under g is g inverse composed of f composed of g on the argument so if the function the function we're using under is three with plus then the inverse would be negative three with plus right and power the operator is f composed with itself n times or n applications of f on the argument and three jodd plus is three with plus or three curry is it plus curry with three or three curry with plus anyway it's plus with a fixed left argument of three is actually a very curious thing that this three with plus pops up in the Ackerman function but it's it seems to be mathematically necessary to have that so now we're going to prove the lemma and you can so more notation this this is not APL notation but it's I think it's used in logic it's implication it's saying that if this is true then this is true all right and you can prove it by induction on the right argument and it's it's not really that hard if you just you know carry through the APL notation so it's the steps progressed by straightforward applications of algebra and the definitions of the APL furnace now in this case we can't really verify the steps if alpha is you know three no if else alpha is greater than two actually because the numbers get too large and it's not really executable at least but at least you can check it on the smaller alphas so so the lemma says that alpha plus one Ack is a power function of alpha Ack basically other than this three you know under three with plus so what this is saying you know what the lemma tells us is that zero Ack is the successor function and one Ack is addition two Ack is multiplication three Ack is four Ack is what they call a power tower is reduction is four right fold yes so four Ack is you're folding with it's with the power function so it grows pretty fast and five Ack is the power function of whatever that is come to the end any questions yes yes um yeah I I think Hasco is a testing utility John Hughes talked about that and it was also mentioned this morning I think as long as it's as long as it's possible for the randomization to select all possible things in the universe then it would be true for all things in the universe because you can you can see that since it has a non-zero probability of selecting each possible index if you run this often enough it will hit all of them I mean it's it's not a practical thing but it's good for thinking about it because I have an expression you know I have an expression which is true on a random index right which may take a long time to hit all possible indices if you actually run it on a large array but as a thinking tool it's used it's very useful to realize that oh this is true for all possible indices is that right okay Aaron yeah the actual element of the result which is z that is true that's true yeah yeah yeah so yeah typically when I test I generate a argument on random numbers over a large range okay it that's not a proof but it's you know it's a good step towards that and as I said you know it's it's also a good good way of thinking about it sometimes you have to write your tests so that it hits the special cases with higher probability otherwise you may never hit that case so for example some some algorithms in dialogue APL kick in when the when the size of the argument or the value is some particular value if you just randomly run through the whole universe you may very very seldom hit that case Morton yes yes once you realize that you may you may do that but I was this is my usual step of writing tests I'm picking random cases yeah except if your universe is huge you may not be able to run through all of them at once oh yeah yeah yeah yeah but once I realize that I this is exactly what I did I run through all the indices which is iota role said okay I think we're out of time but I'm available for any other questions in the hallways thank you very much