An early (but by no means first) exercise might require students to write in Haskell the function
f(x) = x3 + 3x2 + 3x - 2
An answer to this would be:
f x = x^3 + 3*x^2 + 3*x - 2
Notice that multiplication is made explicit with
* and exponentiation with
^. Haskell is essentially a dialect of mathematics with a somewhat different syntax. Seeing the same thing in two different representations starts students on the road to understanding abstractions.
After some familiarity with functions is established, another problem could ask: what function is obtained by composing the above
f with this
g x = x - 3
Multiple answers could be accepted, but all would be approximately of the form
h x = x^3 - 8*x^2 + 12 * x - 11
Later when students are more familiar with functions, problems with an increased level of abstraction could be introduced using Haskell's operator for composing functions. A short example of this operator in use is
h = f.g
which defines the same
h as above.
By focusing on five things:
Pick a subject related to creative precise reasoning.
Such a subject is the concept of a function. Functions are not just for numbers, they can manipulate symbols and even other functions. When approached mathematically, functions require precise definitions. Often a definition of a function requires a creative breakdown into simpler definitions.
Pick a subject where it is possible to ask for creative work and still arrange for an objective, automated evaluation of the outcome.
It is difficult to learn to be creative and precise at the same time but that is exactly what is required when designing any system with interrelated processes. The degree of precision required may vary but the need for it never goes away.
Programming exercises require both creativity and precision. Better yet computers can go a long way towards judging their correctness. Practice with programming exercises prepares students for work planning complex systems that require less precision.
Narrow the goals to those which directly support creative precise reasoning.
This must be done ruthlessly. Otherwise there will be mission creep and other goals will crowd themselves into student brains.
In particular, two subjects normally covered in an introductory course should be avoided: i/o and objects. Both concepts are an important part of programming. However both concepts depend in one way or another on the concept of a function. Both concepts may be learned more easily once that is mastered. Neither concept is needed to acquire the skill of creative precise reasoning.
Provide students with exercises that range from easy to difficult in small steps.
This requires experience on the part of the course developers. Going forward, it will also benefit from interactivity that allows students to share insights and confusions with instructors.
Allow students to work in the dorm or at home while providing immediate feedback.
Using web site technology to assign problems and evaluate answers works here—if the answers allow plenty of room for creativity and can still be evaluated effectively. Exercises involving computer programming can satisfy these criteria.
Provide students who need more practice with more practice.
Not easily done without computer support.
By mastering such a course, a student has a skill which can easily be transferred to other subjects. It is much easier to go from the precision required by mathematics to the precision required by designing fast food processes than the other way around.
The course advocated here provides hands on experience writing arithmetic and symbolic functions in the Haskell programming language. For more particular information see:
Algebras of sets and boolean values are often the first nonnumeric algebras that students see. Haskell can provide exercises involving both concepts. Here is the definition of a function for logical implication
implies x y = (not x) || y
|| means "or".
Here is the same function but named
==> and used like
× as an operator
x ==> y = (not x) || y
We use the word operator for a function whose name is written in symbols
rather than letters and which is used in same style as a
+ or ×
might be used. Notice that we continue to show students examples of how the
same thing can have different representations.
Set union and intersections can be defined as well. Here is some Haskell notation one might use. The definitions of these operators are not shown here. What is shown is an equation asserting that the left hand and right hand sides are equal.
[1,2] +++ [2,3] == [1,2,3] [1,2] *** [3,4] == 
One of the ways to teach students to create abstractions is to show them how to identify common patterns and then to write functions which will generate those common patterns. This can be motivated as a way to avoid repetition.
To make an example: again assume we have used Haskell to create two operators,
***, for set union and intersection. Use these operators
to create generalized union and intersection functions,
gIntersect. Here are applications of the desired functions:
gUnion [ [1,2,5], [3,4,5], [2,5,7] ] == [1,2,3,4,5,7] gIntersect [ [1,2,5], [3,4,5], [2,5,7] ] == 
You can see
gUnion finds the union of all the sets in a set of sets and
gIntersect the intersection.
Here is a definition of
gUnion. It has two parts, the first explains
that the union of an empty family of sets is empty and the second
peels off one set so the its union with the rest can be found.
gUnion  =  gUnion (set:rest) = set +++ (gUnion rest)
Clearly this definition is recursive. The details of how it works don't matter for current purposes. What is important to notice is the similarity of this defintion of 'gUnion' with the definiton of 'gIntersection' below. In particular, notice that if we alter the first definition by replacing
***, we will obtain an exact copy of the second definition.
gIntersect  =  gIntersect (set:rest) = set *** (gIntersect rest)
This similarity can be exploited in Haskell. We can avoid having to write what is structurally the same definition twice by defining a function
makeSetOperation which creates and returns the
gIntersect function from, respectively, the
makeSetOperation we can make it create another function
will behave like one of
gIntersect. All we have to do
g in a context where
f is a parameter and then say
makeSetOperation is defined to be that
g. Here's the definition:
makeSetOperation f = g where g f  =  g f (set:rest) = f set (g rest)
f set (g rest) instead of
set f (g rest) because
f is a function, not an operator. To use
makeSetOperation to make alternate definitions of
gUnion and 'gIntersect' we may pass the arguments
*** to the parameter
gUnion = makeSetOperation (+++) gIntersect = makeSetOperation (***)
The evacuation plan for a large city in case of a nuclear meltdown.
The functions of workers in a fast food restaurant.
A military operation.
Creation of a system of laws that work together.
People can obtain a greater understanding than they tend to think they can but they often have to be brought slowly to it. That is why the course goals need to be kept focused.
People understand things at different rates of speed. An advantage of online exercises is that different students can do different amounts of practicing. Moreover the immediate feedback helps with both understanding and the motivation to keep trying.
A second advantage is that practicing is more relevant than in the typical homework situation because feedback is immediate.
The generalized union and intersection problem shows the need for a slow progression of exercise difficulty. Before students can work with a problem like this they need to be comfortable with recursion and with functions whose domains and codomains consist of other functions.
Problems such as this come towards the end of the course. How easily they are worked depends on what is learned before. Not all students will have everything assimilated at the end. That is normal and the fact that the learning experience is not quite the same for all students does not lessen the value of a course.
The introductory computer science course could be much more useful for general education requirements if some major changes were made:
Knowing things is a first step. The second is to be able to use one's knowledge. That second step is hard to take but luckily once it has been taken in one discipline it is easier to take in another. Four concepts from computer science are particularly good for this.
creation of abstractions to clarify one's thinking
Planning any complex activity requires breaking that activity down into component processes that work smoothly together. These components are abstractions that must be precise enough that their ability to work together can be checked. These components are abstractions that are often created from a blank sheet of paper. Programmers learn early to do such a thing because they must create programs with parts that make sense and work together correctly. Some experience with this is good practice for anybody who would do creative planning of complex processes.
Thinking in terms of functions is clearly of use in any discipline that involves even the slightest use of mathematics. But functions are a part of nontechnical thinking as well. We often say things likes: "our growth is a function of our ability to create." Mastering the precise way of looking at functions makes us more careful in those inevitable situations where we use the concept imprecisely.
use of algebra for more than manipulating numbers
Mathematical logic for example is required in any situation where careful reasoning about correctness is an issue. Running driverless commuter trains and planning automatic shutdowns of nuclear plants are situations where such reasoning already has been applied.
In physics and chemistry, students are taught to think in terms of units. (You don't multiply velocity times distance because velocity is distance/time.) Data types involve exactly the same kind of reasoning but student programmers get more practice in making them up for themselves.
Exercises requiring creative precise reasoning can go a long way towards developing skills—provided they are assigned in graduated difficulty and provided students receive good feedback in a timely manner.
By asking students to write the results of their reasoning online in a language which a computer can "understand", we can provide good immediate feedback. For an instructor to judge the accuracy of creative student thinking requires time and effort. By responding or not responding in the expected way, a computer can do this very quickly.
With such support from technology, a good teacher can focus her feedback on elegance and readability. A poor teacher will do less damage.
A conundrum that has made it difficult to teach creative precise reasoning is this:
Less structured exercises that require precise answers are possible if the answers are in a dialect of mathematics. The Haskell programming language is a dialect which a computer can interpret and act on. Therefore using Haskell we can ask questions of students that require them to formulate their own answers while we remain able to use a computer to make a quality judgement about what they write.
The feedback a student gets from this process is timely and relevant. It frees a teacher of the tedium of judging the accuracy of a less than well written answer. It frees teachers, for the easier, but still important task of informing students about the clarity of their writing.
All programmers need at least some skill at creative precise problem solving. The kind of introductory course advocated here would not be a detour from the development of computer scientists. It would merely rearrange the order in which things are taught.
Currently most introductory courses teach the details of a general purpose computer language. Because Java is the language with the widest employment interest, Java is the language being taught. We are asking all our introductory students to be proficient in the language de jour of professional programmers.
Even those students who become fluent in Java are not ready for the advertised Java jobs. A fluent knowledge of the Java language does not make a Java programmer any more than a fluent knowledge of English makes a writer! One needs an ability to create and organize ideas for that.
Practicing creative precise reasoning before learning a programming language in detail will not slow down the development of computer science majors. Rather it will give them practice of creative problem solving within their own discipline and, by putting off the thorough learning of a programming language, it will make it bit more likely that the language they learn will be the language of choice when they graduate.
Although the name varies, all colleges have general education requirements and these requirements involve some form of creative precise reasoning. In spite of the prevalence of such a goal, students are not developing the desired skill set.
In their carefully researched book "Academically Adrift" Arum and Roksa zero in on the best research they can find for how well our colleges are teaching "critical thinking, complex reasoning, and writing" skills. What they find is that "American education is characterized by limited or no learning for a large proportion of students ..." (page 30).
Within living memory, creative precise reasoning was taught in humanities classes by having students write and professors carefully read papers and in mathematics by having students write and professors carefully read proofs. As Arum and Roksa point out, today's faculty are expected to spend more time on research. The faculty time for careful feedback of student work has been greatly reduced.
Of course, many people are trying to harnass computer techology to fix this problem. As one example, here is a description of an effort by a philosophy professor in Australia. The effort has a major weakness: "Reason!Able on its own does not provide any significant feedback on the user's activities." Indeed computers are not, per se, very good at giving reasonable feedback for creative precise reasons. We can fix that: