Codea Calculator -- in "Agile" style

Impossible to check 3D camera settings with a test, I think, which is a pity. Many a time, my camera is pointing in the wrong direction ~X(

@Ignatz Just check the matrix. That’s what I do for 3D.

I’m not Neo B-), unfortunately. I don’t find the rotation part of the matrix very readable.

@Loopspace good point. But the difficulty is How to define the ‘good’ direction from the in-game perspective with some simple words or manual operations to feed a test suite?. I personnally cannot easily convert a ‘good’ direction in the dunjon into the expected 16 coefficient matrix … :-??

@ignatz lol! we just did it again!

:slight_smile: I’m glad to see some interest in the thing.

I like both of Jmv38’s proposed refactorings, and I prefer the one with the operator table rather than the .mult, though both are clear.

If I were to do another article on the calculator, I would have moved in that direction. Here’s why:

Remove Duplication

In my existing one, there is duplication. The duplication I have in mind is the four (going on more) if statements, one for each operator. These are all very similar.

So what one does is look for ways to extract the unique bits and then use them from one “same bit”. In this case the unique bits are the keystroke, and the function. These are linked: the key implies which function.

Pull out complex inner statements

We might first look at Jmv38’s #2. This adds a bit of clarity, by simplifying the if, but it doesn’t remove the duplication. We might keep it for clarity, or we might be “oh well, that doesn’t help enough”. But we see, we know, we implemented – that there is a 1:1 mapping between the operator ±*/ and the function. This calls for a table.

Table makes duplication still more visible

Imagine we aren’t smart enough to see immediately that we can collapse the four if’s into one, but we do the table. Then we’d see four ifs that look like:

if key == "*" then
  self:doPending(operations["*"])
else if key == "-" then
  self:doPending(operations["-"])

and so on. We realize we can instead, in each case, say

  self:doPending(operations[key])

Now the lines are entirely the same except for the if part. Therefore the ifs can be done with an or:

if key == "*" or key == "-" ...

Just removing duplication takes me a long way

What’s interesting is that I find that if I just remove duplication, one little bit at a time, in obvious ways, the code gets better and better. I enjoy working that way as much as I enjoy imagining some whole new way to do something and leaping to it. And the inch by inch approach, for me, is kind of calming, while the wild leaps always have a bit of risk to them.

What about the check for nil?

Jmv’s next step, just grabbing the value from operations and then checking to see if we got anything, is interesting. It has the neat advantage that if you do it you can add binary operators to the table all day and they all just work.

But I personally do not find that step obvious. It’s clear once done. I’m just saying I don’t have a trigger that goes off in my mind that tells me I can collapse that list of ors into one by checking whether the table returns nil.

Partly, I think, I don’t see this because in most cases, functions that may return something useful and may return nil are problematical. The reason is that everyone has to check the damn thing to see if it’s nil or not. Much better to write functions that are well-behaved and always give you something you need.

So I have a built-in resistance to that trick. In this case, however, it might be the best thing.

But is it?

Note that for = we do doPending(nil). Note that nil is what we got back from the table. Therefore, had we not checked for nil, but not put = in the table, = would do the right thing.

THEREFORE … we really only have two cases, digit and non-digit, and all the non-digits can do the same thing. So we might prefer code like this:

if self:isDigit(key) then
  blah
else
  doPending(operations[key])

When we think about that, we realize we are wide open to wild things coming in that aren’t digits or operations. We probably want those to be some kind of error. We’ve perhaps discovered an issue in our spec, since that wasn’t part of our original concerns. I’d like to at least make the thing bulletproof against that. Quite likely we have it set so that everything that isn’t a digit or operation works like =. That might be OK.

So is the f == nil trick a good one, or is it better to fear that trick and look further? Trick question: the answer is “neither”. What’s better is to see what the code suggests to us, think about it, consider alternatives, and try the interesting ones.

Aside - Learned something …

I didn’t realize one could put local stuff outside any functions. That’s rather handy. I guess it’s just visible inside that source tab then? There are a number of things I’ve been storing in member variables that might be better that way (in other scripts, not sure about this one).

Seems risky, though, in that the locals will be shared among all instances. So might not be good for things that change? (On the other hand, it makes me wonder what one might do for inter-object communication …)

Bottom Line

All the stuff written in the original article and in this thread is stuff that goes through the mind of a programmer as s/he develops code. The more of it s/he considers, the better things are likely to go.

For me, conversation is helpful and multiple ideas are helpful. So I like to pair program whenever I can.

For me, going in tiny steps, using simple rules like “remove duplication”, makes programming a very calm thing. I like that. It lets me pick things up and put them down much more flexibly. I don’t get in that mode where if anyone talks my house of cards is going to collapse.

Anyway, I’m glad this has caused a little thinking, and brought out some ideas. That’s the point. It’s not so much “teaching” as “showing”. It’s just “Look, here’s how I do this.” If people find it interesting or useful, that’s great. If not, that’s OK too.

Wow

This should almost be another article. Would need context, maybe. Maybe not. I think I’ll copy it to the site and modify it a bit.

Thanks for reading and commenting.

Don’t think I’ve forgotten the question about the GUI.

GUIs are a ******* to test. So I prefer not to test them. What I do instead is what we did here, namely build a very solid object with all the logic in it, and then build a very simple, downright stupid GUI that just sends simple messages to that object. In our case, the GUI will just send whatever key each button stands for.

That reduces the possible errors in the GUI to a very few. Offhand, we could have a dead button, or one that sends the wrong character. The latter is rather likely, because we’ll probably cut and paste code to build the GUI.

So we might write some simple tests to be sure that works, although it’s pretty tempting just to run it, type every key and see what happens.

When I’m “on stage”, I’m pretty disciplined about testing everything. In real life, I’m as lazy as the next person, and lazier than the one after that. So often, I don’t work this way. It’s fun juggling lots of objects and if you don’t care what happens, it doesn’t matter much.

But when I get into a long debugging session, it tells me I’d have been better off the other way.

I guess I have to do two more articles, one on refactoring further, and one on the GUI. We’ll see how far I get. Might not be immediate, depends what happens in life. I have family activities today and am off to New Jersey for a week tomorrow. Giving a class in this kind of stuff, it turns out. Last one of the year, I hope.

Thanks!

@RonJeffries thanks a lot for your thoughts!
and concerning:

I guess I have to do two more articles, one on refactoring further, and one on the GUI

YES!!!

Concerning

What about the check for nil?

actually i did not intend to do this. First i just wanted to include everything into the table, including “=”. But “=” doesnt have the same output structure as other signs: it doesnt output a function. It is not an ‘operation’. Then all the options looked wrong:

  • I could have set operation["="] = nil but this means something special in lua: remove “=” entry from the table. It is not what i want to do, specially in a tuto program.
  • I could have set operation["="] = function() return "=" end and furter change your code to test this case. But that would be building unecessary complexity into your code, and mean lot of changes.
    So i just thought i’ll keep your structure with equal being special. Then the natural way to do that in lua was my f = … and checking for nil. That is a very usual way to do it in lua, you’ll see that when you get used to the langage. Because ‘nil’ evaluate as false in an if… statement. So anything existing is true (except false). I learned that reading other’s code (like Loopspace).

Side remark about 'nil' evaluate as false, you may be interested to know.
Many times it makes it very simple to test things. The only gotcha is when you want define default value for functions, when the argument is missing:

function f(a)
   a = a or aDefault

this works great for all values for a except when a==false. Then a is replaced by aDefault, which is not what you wanted. Caught me at least 10 times in last 2 years…

New article: http://xprogramming.com/articles/codea-calculator-ii/

I understand the nil trick, and my habits from object-oriented work lead me to want never to return nil because (in general) it requires me to check for nil a lot.

I suspect there is a way to get the best of both worlds but it is probably clear enough in either form. I was thinking ='s function might just be function return nil end, but didn’t try it

I don’t think there are “best” answers here, particularly not in general. The thing is to think, consider, decide with eyes open.

Thanks!

Wow - lots of interesting discussion going on here. I think I’d better go read those articles.

EDIT: just a tip for you @RonJeffries, when you converted the number back to a string you did this "" .. (self.op(self.display, self.temp)). Lua includes a nifty function tostring that converts (numbers, booleans, etc.) to string for you, so that could be written as tostring( self.op(self.display, self.temp) )

Unless your way has a specific reasoning?

Something that occurs to me:

Even if you don’t include “=” into the table, you can remove that extra duplication by saying

if key=="+" or key=="-" or key=="*" or key=="/" or key=="="then
        self:doPending(operations[key])

This will work because when the key is “=” you pass nil, and since operations["="] is nil, you will still be passing nil.


Taking this a step further I would like to remove all those if statements, mostly because as you add more operations it will get messy. I see myself doing it this way:

local keyIsInTable = operations[key]
    if keyIsInTable or key == "=" then
        self:doPending(keyIsInTable)

In the interest of readable code I use the variable name “keyIsInTable”. For me this provides the best of both worlds between Ron’s way of making it very clear what will allow this test to run and Jmv’s way of simplifying things.


Of course, with the “=” not being a part of the table it can be a little confusing. So I thought, let’s add “=” to the table. And that gets me this:

operations["="] = function(d, t) return d end

function Calculator:press(key)
    local keyIsInTable = operations[key]
    if keyIsInTable then
        self:doPending(keyIsInTable)

Because pressing “=” doesn’t clear the display, the function just returns the display, so when it runs the display will be reset…to the display (so it won’t change)

I guess this could introduce its own confusion so between this and my previous step I’m not sure which is preferable. That said, it works, all the tests run, and the code is ‘cleaner’ than before.


So those are my thoughts / my ideas on how to simplify further.

I’d love to hear your thoughts @RonJeffries, specifically on which step you would stop at and why.

Thanks!

hello @jakAttak my view on your code above (i know you didnt ask for it, sorry to intrude) is: too much thinking for me is needed to understand why it works… All the point of Ron’s choices, sometimes a bit heavy, like the 'if or or … ’ list, is KISS. I think. But we’ll see what he thinks.

@Jmv38, of course you may comment, all opinions are welcomed :). I suppose you are right - I often try to slim my code down to be compact without thinking too much about how others will see it - I work solo so it is a liberty I have not needing to have anyone else understand my code

Hi, Jak and all.

the … thing is not as good as tostring(), which i had not run across. also, i’d have to look back but it may have made more sense in an earlier version. anyway tostring() is better IMO.

I saw that the = could be included but did not go there. I think I mentioned somewhere about using an empty or nearly empty function, as you did. I prefer that because I don’t like nil as a flag but that’s left over from other languages. Maybe it’s not so evil in Lua. I have my doubts, though. :slight_smile:

The keyIsInTable() trick is nice and on a given day I might like it. Were I more into Lua’s handling of true false I might like it better. Certainly is a legit thing to do and the name is clear, which will cause a reader to think about it, so I think it’s fine. Might have done it had I thought of it.

It’s interesting, I think, how far one can go with this kind of thing, and every step tends to make the thing better. There comes a time of diminishing returns, especially for something this simple. But think about this: our current version, whichever one we pick, is far simpler and more clear than any of the originals. In a large program, imagine if everything we did was this simple instead of as not quite so simple as the originals. Might we wind up being able to maintain our large program faster and more reliably?

I think we might. In fact, I am quite sure we would, because I’ve done it, and seen it done this way.

So when we practice on a little bitty thing like this, it seems like overkill. But if we could cut the total number of lines in a large program nearly in half, and make it nearly twice as easy to understand … that could be a very good thing!

Thanks!

Ron Jeffries!

You are in the lucky position that I’ve been held back by a moderator (whom I’m certainly tempting with this introduction).

Let me first say in my words what I understood about your article. Your mission is to come up with a calculator, a simple one, but nevertheless one that works. You do so by mainly using tests and you want to show us how this is a good way.

Well, I’ve got some complaints. No. Questions. It’s questions that I’ve got.

.1. Why does your calculator initally show an empty string? I think we can agree upon the functionality of a good old calculator, and they all show a “0” when turned on.

.2. Your first test in short reads “13*3=”. Later in the text you state that your specific way of testing follows TDD. True to TDD, wouldn’t the first test be something like this?

c = Calculator()
c:displayIs("0")    -- how I would do it
c:displayIs("")     -- alternatively Ron's outset

By starting with “13*3=”, don’t you have too many problems at once?

.3. By focussing on the simple things, you might have proceeded with just digits. There are not too much of them, so I assume that the idea of typing “00” would have appeared early on. It’s not totally wrong but unusual to see leading zeroes except for the single “0”. I know that this is a boring part of the calculator and you may have deliberately ommited this case in favor of more interesting ones. This is just an improvement idea.

.4. Your first calculator skeleton denies every input (showing us failing tests, good). What you then do is to allow every input. Isn’t this like bombing a hole into a wall just because you want to get through but didn’t have the patience for a door?

.5. What you do next looks like the equivalent of putting bricks into the hole because you really wanted a door. You’re doing this brick by brick, so what you have in the meantime is the remainders of the old wall, gaps, repaired parts, the area that is supposed to be the door. Isn’t this dangerous?

.6. The code just before “Story complete.” I can type “10x1x1=” into the calculator and the result is “1”. This is clearly wrong. However, I’ve got no exceptional results from the calculator. Yes, I know, we’re at the very beginning, it has bugs. But it is an interesting enough case. The bug can be found by creating a test that tests e.g. “10x1x1=10”. This is simple because I can think of such a simple test, there is nothing really difficult present at the moment. But if I don’t precisely define the wanted funtionality and just write tests instead, how do I know that I have written enough tests so I can say that everything works “as intended”.

.7. You start adding tests to your tests. You realize that your tests get a bit unwieldy and you create the check function. However, what I think gets unwieldy is that there are two distinct tests in a function that are not so super readable and also a good context for the human reader is missing. You’re using the word “behavior” in your article, that’s the B in BDD. Wouldn’t it be great to take advantage of BDD? See, you don’t have to wait for a later article. You don’t have to show your reader tests that are “lying around” and then say “Surprise!” and tell them that there is a better way. BDD is so easy that it doesn’t need a long way to introduce. Look at the test from my number 2:

function new_calculator_shows_0()
    local c = Calculator()
    c:displayIs("0")
end

Or my suggestion from number 3:

function no_leading_zeroes()
    local c = Calculator()
    c:displayIs("0")
    c:check("0", "0")    -- explicitly not "00"
end

Or some of your tests:

function simple_multiplication()
    local c = Calculator()
    c:check("1","1")
    c:check("2","12")
    c:check("*","12")
    c:check("3","3")
    c:check("=","36")
end

function three_operands_without_precedence()
    local c = Calculator()
    c:check("4","4")
    c:check("5","45")
    c:check("0","450")
    c:check("/","450")
    c:check("1","1")
    c:check("5","15")
    c:check("-","30")
    c:check("1","1")
    c:check("9","19")
    c:check("=","11")
end

See, this approach is simple, at the same time it gives the human reader valuable information. It is also a (more or less rough) specification of what the calculator can actually do. This is a very strong suggestion from me.

.8. Why is self.temp at the point where it was introduced never initialized? In an early version of the calculator you could type e.g. ‘0=’ (or with my favorite calculator that initializes with 0 a simple “=”) at the very beginning and get an error. Isn’t it considered to be good programming style to initialize variables?

.9. You state at some point that you probably should limit the else clause to digits. What does it mean, and why not do it immediately? Is there a chance that I have a broken calculator at that stage? How broken?

.10. Many many pages later and going through a labyrinth of ideas and code patches, we’ve got something that might be defined as a milestone. This is also many pages after number 6, I might restate my question. Without a (small) specification, either in prose or like shown in the BDD section, how do I know what the calculator can do for sure? We know that operator precedence is one of the things that is not done properly. Are there other problems? How do I know? Are there unknown failures where I don’t get an exceptional result but a seemingly good result that is wrong (like with missing operator precedence). How much tests do I have to write to know this. Is this possible with tests at all?

.11. (Warning: strong opinion ahead, not an accusation.) From the intermediate coding steps I get the impression that the calculator got its current functionality by chance. Especially the code that follows the headline “Do the math. Finally!” looks like it was completely driven by the need to pass a test and the concept of the necessary internals of a calculator have been dismissed for that purpose. Well, things get better later, but as I said … rather by chance.

.12. Be aware that there are already people following you. You’ve got some “authority” and people may regard your style as a good one. So as a teacher of TDD you should give your very best. Really.

Codeslinger,

You seem proud of needing to be moderated.

You do offer some interesting ideas. Perhaps tonight I’ll go over them one by one.

What I do in articles like this is show what I really do. It is common in programming books and articles to show some perfect way of doing things. I suppose one could craft an article that looked as if it had been written by some kind of perfect being. That’s not my mission.

I am not a perfect being, and I don’t expect that there are many perfect beings reading along – why would they?

What I do is show what I did, and what happens. I say what I notice, what I think about, why I try what I try. I start where I start. My next step is the one that seems best. Sometimes it’ll be too boringly small. Sometimes it’ll be too big and disaster ensues.

In my experience, that’s what happens in real programming. We are where we are, we go where we go, using our best thinking and the best approach we can muster at the time, and we wind up somewhere. To the extent that our thinking and approach are solid, we wind up somewhere better. Sometimes we wind up somewhere worse. Wherever we wind up, that’s what goes in the article. My articles are written from top to bottom. I don’t go back and revise history or what I thought.

Hindsight is interesting, and we can learn from it, sometimes. But we are not allowed to go back on our own timeline and change the past. We did what we did, thought what we thought.

Your “by chance” comment is interesting and not atypical. When we work in little bitty steps, as I describe here, the shape of the program does seem just to emerge. In an important sense the sequence of events is not planned. We don’t sit back and say first I’ll do this, then that, then this other thing, and then try to make that happen. We pick some next thing that seems like a good idea. We write tests for it, we make them run, we improve the code, we repeat.

This is not waterfall. It is not what is taught in most courses, not what is written in most books. This is how the top Agile programmers do things. If you find it interesting, try it. If, trying it, you find it useful, add it to your bag of tricks. If not, that’s ok too.

Perhaps this evening, I’ll look at your specific comments and reply.

Thanks,

I like all the calculator talk. It takes me back to uni days. One of the things we did there was build up a reverse polish notation calculator. They are really nice for the technical solution, although it’s a bit harder for humans to use cos we’re used to normal calculators.

One thing I never bottomed out was whether trying to do a “normal” calculator, beyond the dumb implementation you see on most calculators that ignore BODMAS etc, is whether you are better coding for BODMAS or save stuff into an RPN form and process from there… Must dig that stuff back out of my brain some day…

Hi space …can I call you space?

I used to like my RPN calculator, but IIRC I never really built up a very deep stack. Probably my mind isn’t very good at recursion. Probably few of us are.

Even BODMAS (the term was new to me, though not the concept) is likely too much to keep in one’s head. So I keep notes on paper, or use a spreadsheet, because these artificial aids make up for my limited brain power.

There is a possibility that occurs to me: maybe the simple style of calculator, with just a few unary operators, and binary operators that all trigger as soon as the next comes along, is pretty close to what works best for the user.

Interesting …

Our purpose here, I think, is to share info and ideas on Codea, and “how” to program with it. So we probably can’t go too deep on the philosophy.