Exploring Ideas with Code

Art of Teaching, Sarah Lawrence College

Notes by Patrick Hebron

Software as Architecture:

Perhaps the best metaphor for computer science is architecture.

When an architect designs a building, he or she does not start from scratch, but instead draws upon a set of relatively simple building blocks. None of the individual pieces are particularly complicated. Rather, the skill of being an architect is in knowing how to combine these simple building blocks to solve complex problems.

There are often many ways of solving any particular problem. For example, if we need to hold something up against the force of gravity, we could use pillars, arches, etc.

While numerous approaches may solve a particular problem, each approach has its own unique properties that may impact the overall design in other ways. For example, on the practical side, an arch might require more expensive building materials or take up more space than pillars. On the aesthetic side, we might simply like the appearance of arches more than pillars.

So while we might solve one small problem in a wide variety of ways, as we design an entire building, we have to think back and forth between these decisions and consider how each decision impacts each other.

This is where the process becomes complex and artful. There is no one solution to any problem. There is no one correct way to build a building. Instead, an architect must learn to make a series of interrelated decisions which in combination solve a set of complex problems.

Though there are infinite ways to solve these component problems, the choices made by an architect are not arbitrary. These component solutions must work together to solve larger problems of how traffic will flow through a building, how resources like water, heat and electricity will be delivered to different regions of the building, etc.

The skill of being an architect is in knowing how to break the "high-level" needs of the people using a building into small component problems that can be solved with simple building blocks. As an intellectual process, this means looking at the system at many different scales simultaneously.

So, the real skill in being an architect has less to do with understanding any particular building block or material than it does with understanding a particular way of thinking about problem solving.

This same set of problem solving skills can be applied to computer science, and I would argue, to most creative and intellectual processes.

In computer science and electrical engineering, we have a similar set of building blocks, which are combined to solve complex problems.

Logic Gates:

In everyday language, we generally use what logicians call fuzzy logic, which relates to approximate, rather than exact reasoning. For example, we might identify an object as being "near", "small," or "slightly green." These statements do not hold exact meanings and are often context-dependent. When we say that a car is small, this implies a very different scale from when we say a planet is small. So, to describe an object in this way requires some auxiliary knowledge about the range of possible values within a specific domain of meaning. Computers do not have access to this sort of auxiliary knowledge and must instead rely upon binary logic, which deals with expressions that must be either true or false.

Binary logic can be thought of as a subset of fuzzy logic.
For the fuzzy concept of "size," we could think of a range in which 0.0 represents the size of a sub-atomic particle and 1.0 represents the size of the universe with infinite points between these two extremes.
In binary logic, only the extremes are possible. Something is either "not big" (0.0) or "big" (1.0).

Much like how algebraic operations such as addition and subtraction can be applied to numeric values, logical operations such as and and or can be applied to binary values (also called boolean values).

And:
( TRUE and TRUE ) outputs TRUE.
( TRUE and FALSE ) outputs FALSE.
( FALSE and TRUE ) outputs FALSE.
( FALSE and FALSE ) outputs FALSE.

Or:
( TRUE or TRUE ) outputs TRUE.
( TRUE or FALSE ) outputs TRUE.
( FALSE or TRUE ) outputs TRUE.
( FALSE or FALSE ) outputs FALSE.

Xor (exclusive-or):
( TRUE xor TRUE ) outputs FALSE.
( TRUE xor FALSE ) outputs TRUE.
( FALSE xor TRUE ) outputs TRUE.
( FALSE xor FALSE ) outputs FALSE.

At times, we might want the opposite of one of the above ideas. This can be achieved with the logical concept not.

Nand (not-and):
( TRUE nand TRUE ) outputs FALSE.
( TRUE nand FALSE ) outputs TRUE.
( FALSE nand TRUE ) outputs TRUE.
( FALSE nand FALSE ) outputs TRUE.

Any logical expression can be formed in an infinite number of ways. For example:
"A exclusive-or B"
is equivalent to
"( A or B ) and not ( A and B )"
as well as
"( A and not B ) or ( B and not A )"

Of course, some ways of expressing an idea are more succinct than others.
For example, "true" and "not not true" are equivalent but not equally succinct.

However, the computer's processing of "true" vs "not not true" will differ by less than a nanosecond.
Therefore, in general, you should write logical expressions in whatever form seems most conceptually straightforward to you without worrying about whether they are optimally succinct.

Conditionals:

In a computer program, we use these sorts of logical expressions to control the program's behavior in response to user actions.

A computer program can be thought of as one long sequence of instructions. However, we often do not run this entire sequence from start to finish. Rather, the sequence will generally contain branching decision points that allow the computer to jump to specific points in the code in accordance with what the user wants to do within the program. This is much like a choose-your-own-adventure book: "If you want the prince to enter the castle, go to page 25."

This is called a "conditional expression." Conditionals are in some ways the heart of programming. In computer science, it often called an "if-else" statement, though the syntax varies by programming language. But the basic form is this:

if( SOMETHING is true ) { perform a set of instructions }
else if( SOMETHING ELSE is true ) { perform a different set of instructions }
else { perform a different/default set of instructions }

So, for example, we might write something like:

if ( the mouse is over the "new window" button and the mouse is pressed ) { create a new window. }
else { do nothing. }

Or:

if ( the "right arrow" button is pressed and hero avatar is touching wall ) { rotate hero avatar and move it left by one step. }
else { move hero avatar right by one step. }

In Scratch, "if" and "if-else" blocks look like this:

Loops:

Very often, we need the program to perform a set of instructions on repeat or some set number of times.

In the lefthand code block above, the program starts, checks whether the avatar is touching the mouse, moves the avatar if it is touching and then ends the program. It will take less than a second for this entire program to run and you won't have a chance to get the mouse to the avatar to even try it out.

To keep the program "alive" and able to interact with the user over time, we need to perform this sequence on repeat.

In the righthand code block, we've wrapped our conditional in a "forever loop." When we get to the end of the block, we go back to the start and run through the sequence again. Now, the program will remain active and will move the avatar anytime it is touching the mouse.

In other cases, we may not want to perform a set of instructions "forever" but instead for a set number of times. Scratch and other programming languages tend to provide a few different forms of loops for use in different scenarios. For example, we might want to repeat a block of code a set number of times or until a particular condition has been satisfied.

Scaffolding:

As with constructing a block building, developing software architecture is an iterative process.

With blocks, a student might assemble a few pieces into an arch, test its strength and appearance before adding additional blocks on top. At times, a particular way of doing something seems correct until we reach a later decision that causes us to go back and revise our earlier choices - perhaps an arch is too narrow to accommodate the flow of traffic or hold up a large building, etc.

This same sort of iterative process applies to programming. You build a small piece, experiment with it and see how it solves one isolated part of the overall problem. Once it does, you connect it with other pieces and then revise as new challenges arise.

One of the nice things about Scratch as opposed to text-based programming languages is that Scratch's building blocks only allow to connect pieces that logically connect to one another. The shape of each block visually demonstrates which other pieces it can connect to.

In addition to the logic blocks discussed above, Scratch offers a wide assortment of blocks that relate to various programmatic and animation concepts. Some of these are more about stylization than programmatic logic. These are fun to experiment with, but the logical elements ultimately hold the whole thing together - they are the building's foundation.