On this page:
1 Concision from Racket
2 Protection from Racket
3 Ht  DP and its *SLs
4 Balance for Students
5 I Want It All, I Want It Now
7.8.0.8

Teaching Languages (*SL), Not Racket

08 June 2014

I often get emails from instructors that report on their experience with HtDP and its teaching languages, called *SL (short for the collection BSL, BSL+, ISL, ISL+, and ASL), and request their favorite ways of strengthening the latter. And indeed, Racket and its cousins come with all the conveniences of modern programming languages. For example, Racket programmers can use pattern-matching in function definitions, define algebraic data types with define-type, de-structure instances of the latter with type-case in a checked manner, iterate over compound data with comprehensions using conventional for syntax, and so on.

Why do HtDP and *SL shun these conveniences?

Don’t they come with all kinds of advantages that would benefit students?

This note is a full answer to this question. The first two sections make a strong argument how HtDP and its *SLs might benefit from algebraic data types and pattern matching. The remaining sections explain why *SL nevertheless does not includes such features right now.

1 Concision from Racket

Let’s start with a concrete example. The left side of figure 2 displays a simple program from HtDP part 1. It starts with two structure type definitions, followed by a data definition that introduces the collection of Shapes and specifies what kind of values the structures may consume. Further following the design recipe, the program comes with two data examples. The remainder defines a function, including a signature, a purpose statement, two function examples, and the definition proper. A student in Northeastern’s introductory course is expected to design this kind of program in the third week.

"in HtDP"

#lang htdp/bsl
; 
; structure type defs.
(define-struct circle (r))
(define-struct square (s))
 
; data definition
; A Shape is one of:
;  (make-circle Positive)
;  (make-square Positive)
 
; data examples
(define c (make-circle 1))
(define s (make-square 1))
 
; Shape -> Number
; the area of a shape s
(check-within (area c) pi 0)
(check-expect (area s) 1)
 
(define (area s)
  (cond
    [(circle? s)
     (* pi (circle-r s)
           (circle-r s))]
    [(square? s)
     (* (square-s s)
        (square-s s))]))

"in Racket"

#lang plai
(require test-engine/racket-tests)
; structure type & data def.
(define-type Shape
  (Circle (r positive?))
  (Square (s positive?)))
 
 
 
 
 
; data examples
(define c (Circle 1))
(define s (Square 1))
 
; Shape -> Number
; the area of a shape s
(check-within (area c) pi 0)
(check-expect (area s) 1)
 
(define (area s)
  (type-case Shape s
   [Circle (r) (* pi r r)]
   [Square (s) (* s s)]))
 
 
 
(test)

Figure 2: A BSL program vs its Racket version

The right side of figure 2 shows how to express the same program in Racket’s plai flavor. A first glance shows that the program has two blank spaces. The first one results from Racket’s ability to combine the structure type definition and the data definition into one define-type definition, often called an algebraic datatype in the programming language literature. The second white space, near the bottom of the program, is due to the type-case construct used in the definition of area. Roughly, type-case is a specialized pattern matching form; it classifies and then deconstructs an instance of a datatype. Deconstructing means that in each clause type-case binds variable names to the field values of an instance. As a result, such clauses tend to be much more concise than the corresponding clauses in the corresponding cond clauses.

2 Protection from Racket

Racket programs are not only more concise than *SL programs, they also come with protection mechanisms that benefit novice students. Consider the creation of structure instances:
> (make-circle -1)

#<circle>

> (make-square 'a)

#<square>

In *SL programs, a constructor consumes any kinds of values and creates a structure instance. But this instance may or may not belong to a class of values introduced via a data definition. Here neither of the two examples is a Shape in the sense of the data definition in figure 2. If students accidentally create such an instance, they get no warning until it is used in a context that assumes the instance belongs to Shape.

Contrast this with the behavior of constructors defined in the plai flavor:
> (Circle -1)

Circle: contract violation

  expected: (or/c undefined? positive?)

  given: -1

  in: the 1st argument of

      (-> (or/c undefined? positive?) Circle?)

  contract from: Circle

  blaming: use

   (assuming the contract is correct)

  at: eval:1.0

> (Square 'a)

positive?: contract violation

  expected: real?

  given: 'a

Here the constructors themselves immediately signal an error if a student supplies the wrong kind of argument. It is simply impossible to construct a circle whose radius is "hello world" or some such nonsensical value.

Wouldn’t your students benefit from such errors and error messages?

Similarly, the use of type-case also improves how functions themselves inform students when they are applied to the wrong kind of value. In *SL, a cond runs out of options and reports this as an error:
> (area "hello")

cond: all question results were false

Now take a look at the error message issued by the plai version:
> (area "hello")

type-case: expected a value from type Shape, got: "hello"

The error message not only tells students that none of the clauses works for the given value. It also explains why they don’t match. With a bit of interpolation, the student may infer that the area function received the string "hello" instead of an instance of the Shape type.

So why not include define-type in *SL?

3 HtDP and its *SLs

For the past two decades, instructors have praised HtDP as “the best introduction to {object-oriented | functional} programming around.” The preceding sentence uses Kleene notation because the sentence usually says one or the other. It also brings across that these sentiments come from people steeped in programming language theory, for whom this notation is a natural.

Yes, HtDP and its supporting teaching languages assume that students either know algebra or get to know algebra via the study of program design. BSL is therefore an “algebraic” language; and HtDP takes its inspiration from functional program design because it is the best fit for such novices. Still,

HtDP aims to teach principles of program design for all kinds settings.

In support of this goal, HtDP cannot This claim applies to other programming languages, too. Languages with C-style syntax, for example, suffer from a superficial similarity with algebraic notation. and does not rely on an existing, off-the-rack programming language. On one hand,

*SLs eliminate the key problems of Scheme’s syntax and improve some run-time checks and their feedback.

As documented in the the DrScheme papers, students struggle with Scheme’s liberal syntax, which makes too many programs grammatically correct and assigns odd meaning to them.

On the other hand,

*SLs provide minimal support for the principle of program design in HtDP.

Each teaching language corresponds to a stage in HtDP. BSL comes with (conditional) function definitions, which students may know from K-12 algebra courses. To this mix, it adds structure type definition, which introduces the idea of programmer-defined forms of data; structure type definitions are available in a wide spectrum of existing programming languages. BSL+ makes it easy to use lists for non-trivial data. ISL and ISL+ expand these two languages with the goal of managing abstraction.

The

*SL support for design is minimal to eliminate any doubt that the design principles apply in any setting.

As much as we, the authors of HtDP, embrace functional program design, we also wish to use HtDP as a framework for gradually introducing these novice programmers into full-fledged computing and software engineering—neither of which is purely functional.

4 Balance for Students

Shortly after students finish an introductory course sequence, they tend to go to an internship or a co-op. According to Northeastern’s Co-op Department in the College of Computer Science, these students typically encounter scripting languages (about half), mainstream object-oriented languages such as C# and Java (about one third), and unsafe versions of C such as C, C++ and Objective C (the rest). While this distribution isn’t fixed and while it is likely to differ from university to university, I consider it indicative of students’ needs.

If HtDP were to exploit advanced constructs from functional programming languages to teach design, it would do students a disfavor. While the world is slowly waking up to the advantages of functional programming, students will not use functional languages for most internships, co-ops, and introductory jobs. The first course must prepare them for the whole spectrum of ideas they will encounter. The *SLs therefore cannot borrow too many elements from the functional world, and HtDP must teach principles of program design without full-fledged support that functional programmers have on a daily basis.

Hence, HtDP and its teaching languages strike a balance between the inspirational background (functional programming) and the goal of preparing students for the real world of programming. Their co-design is subtle but purposeful.

introductory courses are to use the teaching languages (*SLs).

Over many years, I have learned to present this choice and the idea of balance to my students and my colleagues who teach downstream courses. And I think people have come to accept it.

5 I Want It All, I Want It Now

Having said that, I know that while most instructors use HtDP for introductory courses, some assign it as a text for courses on functional programming and others uses it as a quick immersion in design principles in upper-level courses. That’s great. It is never too late to expose programmers to systematic design.

So, if you are one of those instructors who wish to use HtDP in an upper-level course, especially in a course on functional programming, please extend the teaching languages with those features from Racket that you find appropriate. See figure 3 for how to inject define-type into *SL programs.

(require plai)
 
; structure type with data def.
(define-type Shape
  (Circle (radius positive?))
  (Square (side positive?)))
 
; data examples
(define c (Circle 1))
(define s (Square 1))
 
; Shape -> Number
; the area of a shape s
(check-within (area c) pi 0)
(check-expect (area s) 1)
 
(define (area s)
  (type-case Shape s
   [Circle (r) (* pi r r)]
   [Square (s) (* s s)]))

Figure 3: Using PLAI's define-type in BSL

In general, you should prepare a teachpack that imports your favorite Racket features, specializes them for novice programmers, and exports these revised features for use in your class.