Early Programming
The first computers were little more than electrical calculating engines. They could perform tasks like additions faster than their human and mechanical counterparts. A typical use for these machines would be calculating bills. Each customer would have a punch card for their usage and each item on this would be multiplied by the per-item cost stored in a table, added to an accumulator and then emitted, often on another punch card which would be fed into a separate machine that would type numbers on an invoice.
These operations were very simple, and supporting them in the first generation machines involved wiring them up to perform the task. In June 1948, the Manchester Baby changed this by storing its programs in the same way that it stored data, allowing it to be reprogrammed without being rewired.
Optimal Programming
In 1936, Alan Turing proposed the Turing Machine as a universal model of a computing engine. A Turing Machine had an infinitely long tape containing data. It would read the current cell on the tape, then either write something over it or move the tape left or right and update some internal state. A Universal Turing Machine was one where the tape could contain an encoded form of another Turing Machine. This is the basis of all modern programming - making a general purpose computing machine behave like a special purpose one.
One feature of note with a Turing Machine is that the running time of an algorithm depended heavily on the location of data on the tape. Adding two values together could be very quick or very slow depending on how much the machine had to move the tape to get to each of them.
Although the Turing Machine was a purely theoretical idea, early computers inherited this limitation. Most of them used a storage mechanism that was either inherently serial or had large penalties for seeking. In a modern computer, main memory is Random Access Memory (RAM) and the time taken to read a value is more or less independent of its location. In a machine using mercury delay lines for storage, each value in the line was read in order and could only be accessed one nth of the time, where n is the number of values stored in the line. Magnetic drums had similar limitations, since accessing a value required turning the drum to make it visible. Even modern hard drives retain this limitation, but it is less of a problem since they are generally used as secondary storage (if at all) on a modern computer.
The cost of seeking in early hardware lead to the development of optimal programming, with Alan Turing one of the subject’s principle proponents. The idea behind optimal programming was to ensure that data and instructions that would be accessed together were placed physically close together. This meant that the computer would spend more time computing and less time waiting for data.
In modern programming, this kind of thing is rarely done by programmers, but is still very important for compilers. Modern computers use a memory hierarchy, with two or three layers of cache between the main memory and the CPU. Accessing data from a cache is much faster than accessing data in main memory. Data is loaded into these caches and evicted from them in blocks, and so positioning related data so that they can be loaded together still provides a performance benefit.
- David Chisnall
Programming Languages
The very earliest computers were hard coded machines which could only run one program and needed rewiring to run anything else. In 1948, the Manchester Baby became the first stored program computer, a machine which eliminated the distinction between code and data and used the same storage mechanisms for both, allowing ‘rewiring’ simply by changing the contents of storage.
All modern computers follow this model and as the complexity of the programs to be stored has increased, producing them has become an increasingly complex challenge.
Early programming was done by the programmer writing the machine instructions, sometimes called orders. These would be of the form ‘load memory address 100 into register 2’ or ‘add the contents of register 2 to register 3.’ Each instruction was a combination of an operation and one or more operands (things being operated on, such as register numbers, memory addresses, or constant values).
Remembering the numbers corresponding to operations was difficult and when computers started being able to handle text it became common to use mnemonics which corresponded to operations.
As complexity of programs increased, programmers started keeping libraries of algorithms that they used frequently and could insert into their programs where needed. Complex programs were assembled by combining these blocks. This process gradually evolved into the high-level programming languages used today.
FORTRAN
The distinction between a high-level and low-level language is a constantly moving boundary, but the first language to claim the title of a high-level language is generally considered to be FORTRAN.
A FORTRAN program described the algorithm to be executed in a way that was not tied to any specific architecture. One of the the key innovations of FORTRAN was the GO TO statement, invented by Harlan Herrick. This allowed branching to some high-level concept of a label, rather than a machine address.
FORTRAN took many years to develop. The idea was proposed by John W. Backus in 1953 to develop more efficient methods of programming IBM’s 704 mainframe. The first draft of the language specification appeared a year later and the first FORTRAN programming manual was published towards the end of 1956. Readers of this manual had to wait another six months before they could put their skills into practice, as the first compiler was not released until April of the following year.
- David Chisnall
Objects as Simple Computers
[Object Oriented Programming] to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I’m not aware of them.
- Alan Kay, inventor of the term ‘Object Oriented’
In the ’70s, a number of major developments came out of Xerox’s Palo Alto Research Center (PARC). These included the graphical user interface, ethernet networking and the laser printer. In addition to these was a new way of thinking about programming, known as object oriented design. Rather than viewing programs as a set of subroutines which called each other, as procedural programming encouraged, object oriented programming decomposed a large program into objects. An object is a simple model of a computer, which interacts with other objects via message passing.
Many object oriented languages include the notion of a class. This is a special kind of object which is used to create objects. In class-based languages, an object’s behaviour is defined by its class, which may in turn inherit some of its behaviour from another class. This idea comes from the Simula language, originally designed for simulation. Classes were introduced in Simula to allow general categories of simulated objects to easily share code. These could be refined to represent more specialised types of simulated object. Although object oriented languages inherit a lot of ideas from Simula, it lacked a number of features such as encapsulation that are generally regarded as being requirements for an object oriented language.
Newer object oriented languages, such as Self, Io, or JavaScript, abandoned this notion. The designers of Self noticed that classes inheriting behaviour from other classes and objects adopting behaviour from classes were both special cases of the general idea of delegation. In Self any object can have additional behaviour added to it and can be duplicated by sending it a clone message. A cloned object delegates all of its behaviour to the original object, allowing traits objects to act as templates for common objects in the same way that classes do in more traditional languages.
In an unstructured program, flow is controlled by using jumps. With procedural programming, flow is controlled via subroutine calls and returns. With object oriented programming control flows with message passing operations.
In Smalltalk, the canonical object oriented language, there are no explicit flow control operations at all. There is one built in type of object, called a BlockClosure, which represents a block of code and responds to a value message, which evaluates it and gets the return value. Conditional expressions are formed by sending an ifTrue: message to an object representing a boolean value with a block as the argument. Instances of the True class will execute the block when they receive the message, while instances of the False class will not.
- David Chisnall
Further Reading: Adele Goldberg and David Robson, Smalltalk-80: The language and its implementation, Addison-Wesley Publishing Company, 1983