An image of Lucas Lucas Sta Maria

I'm a third-year Computer Science and Mathematics student at Northeastern University interested in programming languages and developer tooling.

(λx.x x) (λx.x x)

Scheme has Simple Semantics


Scheme languages make good introductory languages.

Scheme-based languages make for incredibly pedagogic introductory languages. Their simple syntax creates simple semantics which beginners can quickly grasp. With almost everything being an expression in Scheme-based languages, we can easily tell beginners that we have atoms and we have function applications, where function applications produce an atom (until we get to functions that return functions).

It makes more sense to beginners that function applications return a value when evaluated, the same way $1 + 1$ returns a value. It’s also not too complex to explain that function applications are wrapped in parentheses, with the first item being the function and the rest being the arguments to the function:

(+ 1 1)

Scheme languages, in general, also avoid the use of mutation. With pure functions, students are better able to reason about their code. For instance, they’ll anticipate that calling the same function multiple times on the same set of arguments will always return the same result.

> (filter even? '(1 2 3 4))
'(2 4)
> (filter even? '(1 2 3 4))
'(2 4)

However, in languages like Java, students have to constantly reason about state and how mutation can affect their program.

// Assume that this has methods that mutate the properties of the instance.
SomeObject obj = new SomeObject(some, args);
// Prints the value of some field
System.out.println(obj.getField()); // One value
System.out.println(obj.getField()); // Potentially a different value

Suddenly, the way that students order statements matters — and it can become a minefield in complex programs. In Scheme, referencing the same identifier in the same scope of the program will return the same value (unless you’ve used set! — which is against the spirit of the language).

Scheme is also intrinsically tied to recursion, especially with its basis in S-expressions. Its premier datatype — lists — are recursively-defined, and recursively-defined datatypes lend to simple recursive functions. It is likewise pedagogic to teach recursive datatypes and how to deconstruct them to create recursive functions.

S-expressions also lend to explicit order of evaluation. For instance, other non-S-expression-based languages have precedence levels for the order of evaluation in things like arithmetic. In OCaml, for instance, function application has higher precedence than operators:

let result = make_some_list a b @ make_some_list c d @ make_some_list e f ;;

This means that make_some_list a b and so on would be evaluated first, and then the list appending operator would piece them together. It’s convenient, but implicit and remembering precedence levels can be too much cognitive overhead for beginners. Scheme, on the other hand, is explicit in that order of evaluation:

(define result
  (append (make-some-list a b)
          (make-some-list c d)
          (make-some-list e f)))

Students only have to know that the inner function calls are evaluated before the outer function calls.

I think learning Scheme-based languages first lend to amazing pedagogic opportunities, providing a solid building block to gradually introduce more complex topics (such as mutation). My general suggestion to those who want to learn is usually to start with How to Design Programs, which follows this pedagogic approach.