Artful Computing

The process of constructing software always starts with a desire and an intention. I have a need and I will build (or get someone else to build) a tool that will satisfy that need. (In the software world we usually talk about requirements - the formal expression of needs.) The intention arises from the decision to do something about satisfying the need - in our context by constructing a software system to make images of a certain type by certain means.

As our needs get bigger and more complicated it gets harder and harder to work out how they can and should be satisfied, and even whether they have been satisfied after a solution has been constructed. Software engineers have worked out a number of methods of systematically trying to understand the world they are trying to change, describing software systems that should produce the required changes, and then building the systems. For many of today's systems there are huge gaps between the statements of needs and the way solutions can be actually constructed. Engineers need to build a series of bridges to cover the gap. Object oriented methods are one method of constructing such bridges. They have proved highly effective in suitable skilled and experienced hands and have certainly facilitated the construction of very complex systems (probably including the one you are using to read this article). 

Object Oriented Programming (often referred to as OOP) is the back end of a set of techniques that start with understanding the problem (Object Oriented Analysis or OOA); working out what a solution should look like (roughly equivalent to producing an engineering drawing) which is Object Oriented Design (OOD) and finally building the system with OOP. It only really fully makes sense as part of this integrated process because much of the power of OO methods comes from their ability to make the connections between the analysis of the problem, the designed solution and the implemented program as transparently clear as possible. Before the introduction of OO methods there were conceptual disconnects between the way requirements analysis was documented, the way proposed solutions were fleshed out and the way programs were constructed. These made it hard to confirm that the essential connections were all in place, and that every requirement stated had been addressed by a feature traceable through a structure in the program. (Make no mistake: this process is never easy when you are building systems sufficiently complicated to do real jobs of work in the World. It does become less than impossible.) 

Object oriented programming can nevertheless be very useful when (as in generative art) the target at which we are aiming is rather fuzzily identified - we are exploring an unknown landscape and though we might intend a certain direction of travel, we might also be very satisfied with arriving somewhere else that turns out to be more attractive than we were expecting. That can work because OOP is a very good way of making tool-kits: bits and pieces (analogous to mecano strips or Lego blocks perhaps) which can be taken apart of put together in different ways. They might help us to do our original job, but also help to do lots of other jobs as well. Part of the skill of OOP is producing re-usable collections of parts: and this includes working out what types of parts will be most useful and devising general methods of connecting them together (e.g. like the connectors on Lego blocks). The Processing system itself is one such box of tools which constructed using OO programming; the parts are objects suchs as line(), ellipse(), PImage and so on. Just about all modern computer graphic user interfaces for the applications that you most commonly use are constructed on top of graphical tool-kits built with OOP and based on object oriented design concepts.


 

Magic Bullet?

It is, of course, not a magic bullet that solve every problem with ease, but it is a powerful tool for making potentially complicated things look somewhat simpler. Powerful tools, however, can also cause damage when used without care, or without sufficient understanding. All approaches to programming can only deliver correct results if they maintain the integrity of the information stored within the software system. (By this we mean that we always assign a consistent meaning to data, and every manipulation of the data maintains a consistent interpretation.) OO programming often helps to protect information integrity, but it also provides subtle paths to damaging integrity in ways that can be very difficult to track down even if you are using the advanced tools and techniques of the professional software engineer. It is therefore particularly important to avoid introducing defects into the software as far as reasonably possible, because they can be harder to find and eliminate once they are there.

The inexperienced do sometimes underestimate the degree of skill and background knowledge required to build complex software using OO methods, particularly as very experienced engineers can exploit OO methods to take what look like shortcuts to the less experienced: the conceptual abstractions provided by this approach allow the expert to keep more understanding of the system safely in their head at any one time. These types of shortcuts, however, only prove to be shortcuts for those who have built the required mental skills by having thoroughly explored the long ways round.

Having said all that, the level of intrinsic complexity of generative art programs is often less than one might think given the apparent complexity of the images. The warnings given above are sound, but since we are not attempting to scale the high peaks of software construction we can usually take a more relaxed attitude to the normal protective measures - often without serious harm ensuing. 

We can get a better handle on these abstract ideas by looking at a specific example.

Consider the possibility of using Processing to simulate the flocking behaviour of birds, for example starlings, which at certain times of year collect in enormous "murmurations" before roosting, creating complex patterns in the sky as they manoeuvre around trying to avoid collisions while staying together.

We believe (for the sake of this example - a gross oversimplification!) that each bird follows a relatively simple set of rules: follow the bird in front - but do not get closer than, say, three body lengths.  Avoid collisions with the birds to left, right, up and down, by staying three wing-spans apart. If you have no bird in front move randomly. There will, of course, also be a slight delay in responding to movements of the neighbouring birds.

The OO approach considers each of the birds in the flock to be instances of a class, where each instance has a unique identity and internal state (the unique attributes of a bird comprising in this case the position and velocity of the bird in space). Every instance, however, uses the same algorithms to respond to a limited number of well-defined stimuli from the external world. (The stimuli might be: "bird in front changing direction", "bird-to-left too-close" and so on.)

We are dealing with a sufficiently simple situation that we might be tempted, probably with justification, to jump straight to prototyping an implementation using the OO programming tools in Processing. There is, for example, a class structure. We can declare the position and velocity variables to be attributes of every object in the class, and provide methods by which an object instance will respond to stimuli coming from other objects (e.g. "too close!") To start everything off we might create a few thousand instances of birds with random positions and velocities. Then we see what happens when we allow all the objects to step forward through time. (There are lots of technical details elided here.)

A non-object oriented programming approach - probably easier to start with for the novice - might store all the birds' x-positions in one array, their y-positions in another, their height above ground in another, velocity and direction in other arrays and so on. The algorithms would iterate over all the elements of these arrays. In practice, all the same calculations would be performed. The computer would not know any real difference. However, we as the implementers of the program understand it is a different way. 


 

Classes and Sub-classes

Birds can be divided into species, they can also be grouped into higher level collections such as raptors (e.g. birds of prey) and passerines (perching birds) based on common evolutionary descent and shared morphology. We are psychologically primed to make groupings of objects in the World based on common features. It helps us the make sense of what we see around us and simplifying what we need to know about each thing we encounter. A starling will have its own peculiarities of form and behaviour, but we also know that it will have wings because all birds have wings (even if some of them are now too small to work for flying).

We begin to see the advantages of the OO approach for modelling the World when, perhaps, we introduce a number of different types of birds into our system each with different behaviours (perhaps by adding a hawk hunting). Our non-OO program will now starts to look more complex, because we have to provide a complete and separate set of programming for each type of bird and use lots of conditional statements to give the right variant of behaviours. The more types of bird we introduce the more complex it becomes. 

Object oriented programs rely on the use of classes to specify the behaviour of all instances (objects) of that class. They also provide a mechanism of inheritance. We can define a generic "Bird" class (with associated position and velocity attributes), then also define Starling and Hawk sub-classes. If effect we assert something like "A Starling is-a Bird", "Hawk is-a Bird". They all inherit  the Bird attributes (position, velocity) from the Bird super-class and the generic Bird behaviours, but each sub-class can now be given additional attributes and perhaps different variants of specific behaviour. When used wisely and correctly these techniques can lead to a considerable conceptual simplification in the structure of programs. Human readers of the code can see more easily that they are "obviously right" because there is a natural concordance with the way we want to understand the World. Classes have a close connection with the use of abstraction in understanding problems because they can be deployed to make a direct relationship between the abstractions we have recognised in the World and in our design, and the programming code that we actually write. Since it is humans who make the design mistakes this concordance is an important way of avoiding programming errors.

I would like to emphasise that point: the best way of keeping errors out of programs is by making the program as simple as possible and as easy to understand as possible. Make the way it works transparently obvious.

It is possible to make object oriented programs look unnecessarily complex and that leads to lack of understanding by the programmer and a whole new ecosystem of errors that feed on one another. When OO methods are badly used they can give the illusion of a conceptual simplicity which is not actually there. In my own experience, human programmers can cope with two or three levels of inheritance - after that things get difficult because the behaviour of an object that you are actually using may be defined in several different places and it can be hard to keep track of the interactions between the several different levels of definition. Professionally trained software engineers can deploy systematic techniques that help them control complexity and maintain conceptual integrity. For us the best rule is "keep it as simple as possible". Of course, when we are dealing with large systems that do real jobs of work in the World, things get much more complicated. On this scale, keeping things as simple as possible takes a great deal of professional skill, knowledge and experience, and involves correctly deploying a whole range of sophisticated design and programming techniques. 

 

 

Breadcrumbs