28 Pyret vs. Python🔗

For the curious, we offer a few examples here to justify our frustration with Python for early programming.

Python

Pyret

Python exposes machine arithmetic by default. Thus, by default, 0.1 + 0.2 is not the same as 0.3. (We hope you’re not surprised to hear this.) Why this is the case is a fascinating subject of study, but we consistently find it a distraction for first-time programmers writing programs with arithmetic. And if we handwave the details of floating point aside, are we taking our claims of program reliability seriously?

Pyret implements exact arithmetic, including rationals, by default. In Pyret, 0.1 + 0.2 really is equal to 0.3. Where a computation must return an inexact number, Pyret does it explicitly: a key requirement in a curriculum built on reliability.

Python

Pyret

Understanding the difference between creating a variable and updating its value is a key learning outcome, along with understanding variables’ scopes. Python explicitly conflates declaration with update, and has a tangled history with scope.

Pyret is statically scoped, and goes to great lengths—e.g., in the design of a query language for tables—to maintain it. There is no ambiguity in Pyret’s syntax for working with variables.

Python

Pyret

Python has a weakly-defined, optional mechanism of annotations that was added late in the language’s design, which conflates values and types.

Drawing on lessons learned from our several prior research projects on adding types to languages after-the-fact, Pyret was designed with typability from the start, with several subtle design choices to enable this. Pyret also has support (currently dynamic) for refinement-type annotations.

Python

Pyret

Python’s annotation mechanism has no notion of refinements.

To prepare students for modern programming languages with rich type systems, Pyret’s annotation syntax supports refinements. However, these are checked dynamically, so that students do not need to satisfy the vagaries of any particular proof assistant.

Python

Pyret

Python has weak built-in support for testing. While it has extensive professional libraries to test software, these impose a non-trivial burden on learners, as a result of which most introductory curricula do not use them.

First, a curriculum that proclaims reliability must put testing at its heart. Second, our pedagogy places heavy emphasis on the use of examples, and in particular the building-up of abstractions from concrete instances. For both these reasons, Pyret has extensive support in the language itself—not through optional, external libraries—for writing examples and tests, and provides direct language support for many of the interesting and tricky issues that arise when doing so.

Python

Pyret

Modern testing goes well beyond unit-tests. Furthermore, property-based testing is a very useful gateway to thinking about formal properties. In Python, this is only available through libraries.

Pyret has convenient language features—such as the use of satisfies rather than is in tests—to expose students to these ideas in lightweight ways.

Python

Pyret

State is ubiquitous in libraries.

State is an important but also complicated part of programming. Pyret nudges students to program without state while still permitting the full range of stateful programming. This comes with safeguards both linguistic (e.g., variables are immutable unless declared otherwise) and in output (e.g., mutable fields are displayed to alert the student that the value may change or may even have already changed).

Python

Pyret

Equality comparison is simplistic and in line with most other professional languages.

Equality is in fact subtle, and useful as a pedagogic device. Therefore, Pyret has a carefully-designed family of equality operators that are not only of practical value but also have pedagogic use.

Python

Pyret

Images are not values in the language. You can write a program to produce an image, but you can’t just view it in your programming environment.

Images are values. Pyret can print an image just like it can a string or a number (and why not?). Images are fun values, but they aren’t frivolous: they are especially useful for demystifying and explaining important but abstract issues like function composition.

Python

Pyret

The language doesn’t have a built-in notion of reactive programs.

Reactivity is a core concept in the language, and the subject of both design and implementation research.

Python

Pyret

Python’s error messages are not added with novices as a primary audience.

Novices make many errors. They can be especially intimidated by error reports, and can feel discouraged about causing errors. Thus, Pyret’s error messages are the result of nearly a decade of research. In fact, some educators have created pedagogic techniques that explicitly rely on the nature and presentation of information in Pyret’s errors.

Python

Pyret

Python has begun to suffer from complexity creep that we believe serves professionals at the expense of novices. For example, the result of map in Python is actually a special generator value. This can lead to outcomes requiring extra explanation, like map(str, [1, 2, 3]) producing <map object at 0x1045f4940>. Type hints (discussed above) are another example.

Since Pyret’s target audience is novice programmers programming in the style of this book, our primary goal when adding any feature is to preserve the early experience and avoid surprises.

Python

Pyret

Data definitions are central to computer science, but Python over-relies on built-in data structures (especially dictionaries) and makes user-defined ones unwieldy to create.

Pyret borrows from the rich tradition of languages like Standard ML, OCaml, and Haskell to provide algebraic datatypes, whose absence often forces programmers to engage in unwieldy (and inefficient) encoding tricks.

Python

Pyret

Python has several more rough corners that can lead to unexpected and undesirable outcomes. For instance, = sometimes introduces new variables and sometimes rebinds them. A function where a student forgot to return a value doesn’t result in an error but silently returns None. Python has a complicated table that describes which values are true and which are false. And so on.

Pyret is designed from the ground-up to avoid all these problems.