CodeWorld’s Big Decisions
Reflecting back on the last 6 years of developing and teaching with CodeWorld, there are a number of decisions that were unique, and often even controversial, that define the project. For the record, here are eight of the biggest decisions I’ve made with CodeWorld, and the reasons for them.
1. Teaching functional programming
Regular readers of this blog are probably familiar with functional programming, but for those who aren’t, you should understand that it’s really a rather different paradigm from most typical programming. It’s not just another syntax, with a few different features. Instead, it’s a whole new way of breaking down problems and expressing solutions. Basic ideas taught in the first few weeks of traditional computer programming courses – for example, loops – just don’t exist at all. And other really central ideas, like functions and variables, have a completely different meaning.
I’m not quite alone in teaching functional programming, though. Matthias Felleisen and Shriram Krishnamurthi started sizable effort to teach Scheme at the K12 level in the 1990s, and Emmanuel Schanzer created a Scheme/Racket based curriculum called Bootstrap, which is heavily based on functional programming. I’ve made the same choice, and for much the same reason.
In fact, I never set out to teach “coding” at all! My goal is to teach mathematics more effectively. But mathematics education suffers from the weakness that students who make a mistake often don’t find out about it until days later! By them time, whatever confusion of ideas led to the error has long been forgotten. CodeWorld began as my attempt to get students to directly manipulate things like functions, expressions, and variables, and get immediate feedback about whether the result makes sense, and whether it does what they intended. For that purpose, a functional programming language is perfect for the job!
2. Teaching Haskell
Even after the switch to functional programming, I still surprise a lot of people by telling them I teach middle school students in Haskell! Let’s face it: Haskell has a bit of a reputation as a mind-bending and difficult language to learn, and it sometimes even deserves the reputation. This is, after all, the programming language community with more Ph.D. students per capita than any other, and where people hold regular conversations about applying the Yoneda lemma to help solve their coding challenges!
But it doesn’t have to be! Haskell also has some advantages over almost anything else, for someone looking to work with tangible algebra and mathematical notation.
First of all, the language semantics really are comparable to mathematics. Haskell is often called purely functional, meaning that it doesn’t just enable the use of functional programming ideas, but in fact embodies them! By contrast, most other widely used functional languages are impure. In an impure functional language, a function is actually the same complicated notion of a procedure or recipe that it is in an imperative language, but it is conventional (and the language offers powerful features to help with this) to stick to a subset that’s consistent with mathematics, most of the time. That’s often a fine trade-off in a software engineering world, where the additional complexity is sometimes needed; but in education, when I tell a student that a function is really just a set of ordered pairs, I don’t want to have to later qualify this statement with “… except for this magical function here, which produces a random number.”
Even more importantly, basic syntax looks almost exactly like mathematics (or at least, it can). Bootstrap, for example, gets the semantics right, but looking through sample student workbooks, there’s quite a bit of “here’s how you write this in math; now write it in Racket.” By contrast, when teaching with CodeWorld, we’ve been able to effectively explain the programming language as a set of conventions for typing math directly for the computer. There are obviously still some differences – both at the surface level, like using * for multiplication and ^ for exponents, and at a deeper level, like distinguishing between variables and constructors on the left-hand side of equations. But in practice, this has been easily understood by students as limitations and tweaks in which math notation CodeWorld understands. It feels like a dialect, not a new language.
(It’s worth pointing out that Racket also includes a purely functional language subset that’s used by Bootstrap, though the syntax is different. Shriram Krishnamurthi has mentioned Pyret, as well, which among other nice properties closes some of the ground between Scheme and mathematics notation, at least for expressions. You still can’t just write “f(x) = x + 5” to define a function, though.)
So what about the mind-bending parts of Haskell? It turns out most of them are optional! It took some effort, but as I’ll mention later, I have removed things like type classes (including the dreaded monads) and many unnecessary uses of higher-order functions. What’s left is a thin wrapper around notation that students are already learning in Algebra anyway.
3. Using the Gloss programming model
Of course, a programming language by itself isn’t a complete tool. You also need libraries! The next big decision was to base CodeWorld on the programming model of Ben Lippmeier’s Gloss library.
Gloss is an interesting choice on its own. The programming model is very simple. Everything is a pretty comprehensible mathematical thing. It’s probably too simple for sizable projects, and you could make the case that teaching it is letting down students who want to be able to scale their programming skills up to larger projects. But again, it has two advantages that I believe outweigh this concern.
First, it’s tangible. Outside of Gloss, much of the current thinking around building interactive applications in functional programming environments centers around FRP (Functional Reative Programming). FRP defines a few abstract concepts (“events” and “behaviors”), and then hides when they look like or how they work. Of course, strong abstraction is a foundation of software engineering. But it’s not a foundation of learning, or of mathematics! Indeed, Elm also recently (and probably with even less justification, given its less educational audience) dropped FRP in favor of tangible functions, as well. The advantages of concrete and tangible types that students can get their heads around are hard to overstate.
Second, again, this choice better supports building an understanding of mathematical modeling. In addition to it being easier for a middle school student to understand a value of type Number -> Picture, than the more abstract Behavior Picture from FRP (or the even more obtuse non-terminating while-loop of the imperative world), it also gives them experience with understanding how real phenomena are modeled using simple ideas from mathematics. Later programs are built using initial values and step functions, along explicitly bundled state. This gently starts to introduce general patterns of thinking about change in ways that will come up again far down the road: in the study of linear algebra, calculus, differential equations, and dynamical systems!
Of course, there’s a cost here. I wouldn’t point someone to Gloss for a real-world project. Even something as simple as a single GUI component can be complicated and fragmented, since students have to separately connect the state, initial value, behavior over time, and event handling. But the cost in encapsulation is most keenly felt in larger projects by more experienced programmers who can find this sort of plumbing work tedious. Typical introductory programming students still have a lot to learn from connecting these pieces and understanding how to make them work together.
4. Replacing the Prelude
Once I had Haskell and Gloss in place, the next big choice made by CodeWorld was to replace the Haskell prelude with a customized version. GHC, the most popular Haskell compiler, provides a lot of power to customize the language by making changes to libraries. This extends even to the meaning of literal text and numbers in the source code!
One reason for replacing the Prelude was to keep the complexity of a first working program as low as possible. For students who are just starting out, every word or piece of punctuation is an obstacle. Haskell has always done better on this front than Java, which requires defining a class, and a member function with a variety of options. But adding import statements definitely doesn’t fit the vision articulated above of the programming language as a thin wrapper around mathematical notation. So the modified Prelude puts all of the built-in CodeWorld functions in scope automatically, without the need to import additional modules. As a result, a minimal CodeWorld program is one line long.
A second reason for replacing the Prelude was to remove a lot of the programming jargon and historical accidents in Haskell. Some of this is so entrenched that experienced programmers don’t even notice it any more. For example, even the word “string” to denote a bit of text is a holdout from how computer programmers thought of their work in the mid 20th century. (CodeWorld calls the analogous type Text, instead, and also keeps it separate from lists.) Haskell itself has introduced its own jargon, which is confusing to students as well.
But the most important consequence of replacing the Prelude is that advanced language constructs, like type classes and monads, can be hidden. These features haven’t actually been removed from CodeWorld, but they are not used in the standard library, so that students who don’t intend to use them will not see them at all. This made more changes necessary, such as collapsing Haskell’s numeric type class hierarchy into a single type, called Number. Perhaps the most interesting adaptation was the implementation of the (==) operator for equality comparison, without a type class constraint. This was done by Luite, by inspecting the runtime representation of the values in the GHCJS runtime (see below).
5. Intentionally foiling imperative thinking
Sometimes, it seems that the dogma of the functional programming language community (and Haskellers in particular) is that programmers are corrupted by imperative languages, and that a programmer learning a functional language for their first experience would have a much easier time. I haven’t found that to be 100% true. Perhaps it’s because even students with no prior programming experience have still been told, for example, to think of a program as a list of instructions. Or perhaps it’s something more intrinsic in the human brain. I don’t know for sure.
But what I do know for sure is that even with no previous experience, middle school students will gravitate toward imperative semantics unless they are carefully held back! Because of this, another choice made by CodeWorld, and one of the main differences from Gloss, is that it makes some changes to intentionally trip up students who try to think of their CodeWorld expressions as an imperative sequence of instructions.
One example of such a change: in Gloss, a list of pictures is overlaid from back to front. In CodeWorld, though, the order is reversed. Combining pictures, whether via the pictures function, or the & operator, is done from front to back. The reason is that as I observed students in my classes, I realized that many of them had devised a subtly wrong understanding of the language semantics: namely, that circle(1) was not a circle, but instead a command to draw a circle, and that the & operator simply meant to do one thing, and then the next, and the pictures ended up overlaying each other because of the painter’s algorithm. Because of this misunderstanding, they struggled to apply or understand other operations, like translation or rotation, in a natural way. After swapping the order of parameters, students who form such a hypothesis will immediately have it proven wrong. (The analogous mistake now would be to assume that & means to do the second thing first, and no student I’m aware of has made that error.)
A similar situation exists with colors. In Gloss, the color function changes the color only of parts of a picture that don’t already have a color! This means that the semantic model of the Picture type in Gloss is quite complex indeed. Instead of just being a visual shape, a Gloss Picture is a shape where some parts have fixed color, but others have unspecified color, and the color function operates on that value by fixing any unspecified bits to the given color. Indeed, the most sensible way to understand these values is in terms of the implementation: that the color function sets a current color in the graphics context, which is used for that subtree, but only if it’s not changed first. This is a leaky implementation! It is fixed by CodeWorld, where applying a color to a picture overrides any existing coloring.
Another change that helped a lot with this was to carefully remove the use of verbs for function names in the CodeWorld standard library. I observed verbs misleading students many times. Sometimes, they expected that use of a function would permanently change the value of its parameter. Other times, they even expected a function like rotate to turn a picture into an animation that keeps moving! The key idea they are missing is that functions are not actions, but rather just relations between values. Such relations are better (even if it’s sometimes awkward) described somewhere on a scale between nouns and adjectives, rather than verbs. The way the code reads after this change once again acts as a roadblock to students who try to build on an incorrect understanding.
6. Embracing the web
Beyond the programming language and libraries, another important choice in CodeWorld was to strongly adopt the web as a medium. The first version of the platform in 2010 was a relatively early adopter of web-based programming tools! However, the execution model (using SafeHaskell to run student code in a trusted way on the server and stream frames to the client) was definitely doomed from the start. It was a hack, which worked for one class, but was hardly scalable.
This decision was important for a few reasons. The first is compatibility and universal access. Schools have whatever devices they have access to: Chromebooks, bring-your-own-device plans, etc. Students themselves are constantly switching devices, or leaving theirs at home. Depending on a locally installed application – or saving student projects on a local disk – for a class at the middle school level would be a disaster. Because CodeWorld is all web-based, they can work from any system they wish, and have full access to all of their saved projects.
The second reason a web-based environment was important is that sharing is a huge part of student motivation. Because the CodeWorld server remembers all compiled code by its MD5 hash, students can send projects to each other simply by copying and pasting an appropriate URL into an email, chat message, or text message. It is difficult to express how helpful this has been.
Despite the advantages of the web, though, I am hoping to soon have export of student projects to mobile applications, as well. The development environment will remain web-based, but created applications can be installed as apps. It’s likely that someone will be working on this feature over the summer.
7. Supporting mathematics education
Another big decision made by CodeWorld, and hinted at already, was to often sacrifice traditional computer programming education for better mathematics. This has been done with a hodge-podge of small changes, such as:
- De-emphasizing programming concepts like abstraction, maps and folds, and higher-order functions, in favor of approaches like list comprehensions that look more like mathematics.
- Uncurrying all functions in the standard library. This is easily the most controversial decision I’ve made for the Haskell community, but it’s really just a special case of de-emphasizing higher order functions. After uncurrying, functions can always be written in standard mathematical notation, such as f(x) or f(x, y).
- The coordinate plane uses a mathematical orientation. Gloss’s coordinate plane looks like computer screen coordinates, with (0, 0) in the top left. CodeWorld’s plane puts (0, 0) at the center, and it orients the positive y axis to point up. These just match conventions.
- CodeWorld also rescales the coordinates so that the plane extends from -10 to +10 in both dimensions, rather than counting in pixels. This turns out to have been an amazing choice! It simultaneously allows students to do low-precision placement of shapes on the plane without multi-digit artithmetic, and introduces decimals for added precision. In the end, this combination better supports middle school mathematics than the alternative.
Another change here was originally an accident. CodeWorld, from the beginning, did not implement using any kind of image file in a program. Originally, this was because I hadn’t bothered to implement a UI for uploading assets to use in the web-based programs! But after teaching with it, I don’t regret it at all. I’ve had other teachers tell me the same thing. By giving students only geometric primitives, not images copied and pasted from the web, as the tools for their projects, they are more creative and work with a lot more mathematics in the process.
8. Opting for student-led projects
The final big decision on my list doesn’t pertain to the web site or tools at all, but is about the organization of classes. There are a lot of efforts out there to encourage students to learn to code. Hour of Code encourages teachers to devote an hour to programming activities and games. Many organizations are running day-long activities in Processing or Scratch or Greenfoot. Bootstrap started with once-a-week after school programs using Racket, and has scaled up from there. I’ve volunteered as a mentor and team lead for weekend hackathons by organizations like Black Girls Code.
These are great! I wouldn’t discourage anyone from jumping in and doing what they can. But in many cases, they seem to miss the opportunity for student creativity. There’s a tendency for a lot of organizations to create very guided activities, or shy away from anything that might get a student away off the beaten path. Early versions of the Bootstrap curriculum, for example, encouraged kids to build games, but designed a game from start to finish (in terms of generic words like the “player”, “target”, and “danger”), and give students limited creative choices in the process. (Bootstrap has since expanded into a more open-ended Bootstrap 2 curriculum, as well.) Hour of Code consists almost entirely of scripted activities that feel more like playing a game than building one, which makes sense because they are intended to be completed in an hour. The BGC hackathon mentioned above was limited to use of a drag-and-drop GUI design tools, and devoted more time to having students sit in presentations about startup business models and UX design than letting them create something impressive of their own.
So one way that CodeWorld has been different from many of these activities is that I’ve tried to plan from the very beginning of the course for students to decide on, design, and implement their own ideas from the ground up. Sometimes that means taking longer, and taking smaller steps. From the very beginning, projects in the class aren’t plugging bits into a designed program, but rather creating things of their own choosing, at the level students are capable of doing creatively from scratch at that point. It means that I don’t even start talking about games until halfway through the class. But I think it’s important to let students dig in at each step and express themselves by creating something that’s deeply and uniquely theirs. Along the way, they spend a lot more time tinkering and trying out things; even trying out different possible overall organizations of their programs!
I think CodeWorld has been very successful at this. When students in CodeWorld create their own games, they really create their own games. They work differently, and have different designs.
Here are a few examples from various classes, all written by students between 12 and 14 years old:
- Gnome Maze Use WASD keys to help a gnome navigate the maze and find the gold.
- Donkey Pong One player uses W and S, the other uses the up and down cursor keys. Hit the ball back and forth.
- Dot Grab One player uses WASD, and the other uses the arrow keys. Race to eat the most dots.
- Yo Grandma! Save an old lady in a wheelchair from various hazards by dragging attachments onto her wheelchair.
- Jacob the Fish Help Jacob dodge sushi and eat minnows, and avoid becoming a snack for an even larger fish
- Knight-Wizard-Archer A twist on rock/paper/scissors, with fantasy characters
- Popcorn Cat Drop the cat to eat the popcorn, but dodge dogs