Programming Languages and Piaget's Stages of Cognitive Development
I rewatched Doing with Images makes Symbols recently and there was one bit that I had not absorbed before.
In the talk, he explains the cryptic title. He took a simplified model of Piaget's Stages of Cognitive Development: corporal -> visual -> symbolic. He wanted to bring these three stages into relation with each other, so he made a sentence: "Doing with Images makes Symbols". Doing, of course, is the body stage. Images is the visual stage. And symbols is the abstract symbolic stage.
Doing
Imagine the Logo programming
language. Logo is
perfect for small children because it helps tie the visual and symbolic
to their main mode of experience. Young children are primarily focused
on their own bodily experience. They have trouble thinking from other
points of view. But by translating their own actions (moving around)
into symbolic instructions (Logo code), they can see the turtle
performing the actions. Logo's turtle takes on the child's point of view
and makes it visual. The child learns to see his/her own perspective
from the outside. The coordinate system is egocentric. For example,
the FORWARD
command moves the turtle in the direction it is facing.
Smalltalk includes the same egocentric perspective. Each object has its own perspective of the other objects in the system. Smalltalk objects could ask "who are my neighbors in the list I am in". Squeak simulates a space that the objects inhabit which includes collision detection. This perspective, I would argue, is one of the unsung benefits of Object-Oriented Programming: the ability to program from the perspective of a single object at a time, freeing your mind from thinking about the inner workings of the rest of the objects. Objects are not just meant to be strung together but can be "embodied" by their programmer who sees from their unique perspective.
Symbols
The other end of the developmental spectrum is the symbolic. The obvious choice for symbolic programming is Lisp. Programs take on a very abstract quality and require advanced programming techniques. In this perspective, the programmer has a god-like, symbolic semantic of the workings of the system and builds abstractions in a calculus with simple yet powerful rules. Further, the programmer manipulates code in code. The coordinate system is abstract and relational. Shapes are manipulated in an abstract representation, not as pixels occupying space. A value may encode abstract knowledge (line between a and b) instead of specific coordinates.
Images
In the middle is the visual stage. I am going out on a limb, but I would like to posit that Java exemplifies this middle-ground perspective. In this perspective, you think and understand visually. You draw diagrams. You think in terms of archetypal interactions (client-server, MVC, etc). You can simultaneously conceptualize several roles in a single group of interacting objects. The coordinate system is cartesian, taking an external, objective perspective.
This is not to say that Java does not include any corporal perspective, nor that Lisp is all symbolic. I am talking in broad generalities about predominance.
Not one-at-a-time but all-at-once
The interesting thing about Piaget's stages are that they are stages of dominance, not a rocket-stage-jettison progression. That is, we can still, as adults, think in terms of any of the prior levels. In fact, the most successful scientists and mathematicians work on the earliest levels (corporal/visual). Einstein claimed he had body sensations about relativity and his disabilities with abstract symbols is well documented. These three types of thinking are done by different parts of the brain. The parts are always there but take dominant roles at different times in your life.
How this relates to programming language design is that when we are designing a programming system, we should include all of these ways of thinking so that the programmer can choose the best way to think about the problem. The easiest way to draw a circle is from a self-centered perspective in terms of motion and gross actions. The easiest way to plan a protocol is visually with boxes and lines or swim lane diagrams. Once translated into symbolic code, syntactic and semantic abstractions become evident, and can be manipulated as such.
The trick of good language design is to facilitate programming at any level and translation into the other levels. How can we perform this trick?