When I started reading computer programs, they were written in BASIC for personal computers such as the Apple II and the Commodore PET. They appeared in magazines or books.
When you wrote a program in BASIC, your code had complete control over the CPU from the time you entered the “RUN” command. Nothing could happen that was not under your control (if you ignore bugs!)
If you had an INPUT command, then your program would pause until the user typed something and pressed the Enter key. Then your program would resume on the next line.
For greater control, you would use the INKEY$ function so that you could see and process each character as it was typed.
So your code was “procedural”: it would do something, and then it would do something else, all under your complete control.
When Visual Basic came along, things changed. Suddenly you had a series of control on a screen, and each one of them had code attached to events.
This idea of having to respond to events as they happen in an unpredictable order is good. It means that, as a developer, you need to think about your user doing things in a sequence other than the one you were anticipating.
In his book “Getting Things Done, David Allen talks about something he learned in the martial arts which is being in a state of alertness and readiness which he refers to a “mind like water” (which he has apparently trademarked, believe it or not!)
I think this is a good image for programming – your code should be alert and ready to respond to whatever happens next. Your code should “let go”, and be ready to kick into action again then the user (or something else) completes an action. You should not presume what will happen next, and neither should your code.
I remember way back when Basic (probably Turbo Basic) introduced ON KEY and ON COM statements so that you could get code to run (a subroutine specifically) when a specific key was pressed or when something arrived on the serial port. This was exciting because you could have code that ran on an event automatically without you having to check for it specifically. So the user could press F1 for help (for example) or F10 to quit at any point within the lifetime of the program (while those keys were enabled).
When writing a program in Visual Basic (back in the day) you would sometimes write a long-running piece of code. You might be processing every row in a database or a file, for example. If you wanted to update the User Interface (e.g. showing a progress bar) then you would need to call the DoEvents function periodically to yield so that Windows gets a chance to do this.
VB.NET has a Application.DoEvents method for a similar purpose.
DoEvents is a reminder that your code is not the only important code on the computer and your code should be collaborative. Blocking is bad, yielding is good.
I was working with a developer way back on a program which transmitted data between two mini-computers, a development machine and a production machine. Sending a file involves a series of steps: receiving the instruction to send the file; sending the header; sending the data blocks; sending the trailer; updating the log.
I explained to the developer that the program on the development machine might be sending a file and fetching a file at the same time. So the program would need to check each block as it was received to see what it was and what to do with it.
Because she was thinking in a procedural way, she wrote the code to do all of the steps one after the other.
I had to help her to refactor it so that it had a central tight loop, and that loop would have to respond to its circumstances each time. It might have received some data from its peer; it might have clearance to transmit a new block; it might have received a new command from a user; or it might be able to execute the next command from its queue.
This flexibility was needed. When the program finished receiving a file, for example, it would move it to the designated location, potentially replacing an existing file. If that file was in use, then the move would fail, and would have to be retried. If the code was determined to perform that operation right away, and was unable to do anything else while waiting to do so, then the exchange of files would be halted for minutes or even hours!
So a central loop with an awareness of what was happening with various requests, and what was waiting in the queue, was essential to the correct operation of that system.
When you write code for Arduino (a “sketch”), you get to write a “setup” function and a “loop” function. The former is called once, and the latter is called repeatedly.
Writing code in Processing or p5.js works in the same way, except that the function which is called repeatedly is called “draw”.
When you are writing a loop or draw function, you should do what you need to do right now and then finish, confident in the knowledge that you will be called again in a moment. And by yielding in this manner, you give the CPU a change to do other housekeeping tasks.
When the loop restarts, your code must be able to resume and do the next thing.
When I wanted to add graphics to my COVID program for the MXCHIP, I downloaded a library which included a sample program which created an animation using graphics commands.
I was shocked to see that the “loop” function included a “while (1)” statement. You can see the program here.
I was surprised to see that the programmer did not respect the need to yield at the end of each loop. I refactored it to do this and it worked just as well.
When writing my COVID program, I had to decide what I would do in the setup function, and what I would do in the loop.
At one stage I was doing the HTTP GET functions to get the up-to-date COVID statistics from the REST API. But I moved these to the loop and I used a variable called “init” to track where I was in the step for initialisation. When init is 1, I get the data for Ireland. When it is 2 I get the data for the USA. I do one thing each time loop is called and I increment the init variable. When I have completed the initialisation, I set init to 0 and I can respond to button presses during each subsequent pass through loop.
The great thing about this is that if the user presses the two buttons simultaneously, I just set init back to 1 and the fetching of data starts all over again. So not only is being flexible good, but it had an immediate benefits when I wanted to do something new.
It is important to get away from procedural thinking, to write code which is fluid and flexible, to avoid thinking that you have completed control over the CPU, the network, etc., and to avoid making assumptions about what is going to happen next.
As Bruce Lee said:
Empty your mind. Be formless, shapeless, like water.