On CodeWorld and Haskell
I’ve been pouring a lot of effort into CodeWorld lately… and I wanted to write a sort of apology to the Haskell community. Well, perhaps not an apology, because I believe I did the right thing. But at the same time, I realize that decisions I’ve made haven’t been entirely popular among Haskell programmers. I’d like to explain what happened, and try to make it up to you!
Originally, I started this project using Haskell and the excellent gloss package, by Ben Lippmeier. CodeWorld has been moving slowly further and further away from the rest of the Haskell community. This has happened in a sequence of steps:
- Way back in 2011, I started “CodeWorld”, but at the time, I called it Haskell for Kids. At the time, I understood that the reasons I’d chosen Haskell as a language were not about cool stuff like type classes (which I love) and monads and categories and other commonplace uses of solid abstractions (which fascinate me). Instead, I chose Haskell for the simple reason that it looked like math. The rest of Haskell came with the territory. I built the first CodeWorld web site in a weekend, and I had to settle on a language and accept all that came with it.
- From the beginning, I made some changes for pedagogical reasons. For example, gloss defines rotation to be clockwise. I insisted on rotation working in the counter-clockwise direction, because that’s the convention universally used in math. Later, I resized the canvas to 20×20, so that typical programs would need to use fractions and decimals, which is a middle school math education goal. I made thes changes, even though they broke compatibility with a widely used package. Sorry for anyone that’s struggled with this.
- I rebranded “Haskell for Kids” as CodeWorld, and stopped explicitly depending on gloss in favor of just reproducing its general approach in a new Prelude. This was a deliberate attempt to get away from focusing on the Haskell language and libraries, and also to the accompanying import statements and such. This hid the ways that Haskell was a general purpose language with uses outside this toy environment. That is unfortunate.
- I rewrote the Haskell Prelude, to remove type classes. Along the way, I collapsed the whole numeric type class hierarchy into a single type, and even got Luite (the author of GHCJS) to help me with some deep black magic to implement equality on arbitrary Haskell types without type classes. This threw away much of the beauty of Haskell… in favor of dramatically improved error messages, and fewer things you need to know to get started. It was a real loss.
- Finally, I commited the unforgivable sin. I dropped curried functions, in favor of defining functions of multiple parameters using tuples. This finally makes CodeWorld feel like a completely different language from Haskell. That really sucks, and I know some people are frustrated.
Why It Happened?
First, I want to point out some things that are not the reason for any of this:
- I did not do this because I think there’s something wrong with Haskell. I love type classes. I love currying, and especially love how it’s not just a convenient trick, but sometimes introduces whole new perspectives by viewing tedious functions of multiple parameters as simple, clean, and elegant higher-order functions.
- I also did not do this because I think anyone is incapable of learning full-fledged Haskell. In fact, I taught full-fledged Haskell to middle schoolers for a year. I know they can do it.
So why did I do it? Two reasons:
- Teaching mathematics has always been more important to me than teaching Haskell. While Haskell is an awesome programming language, mathematics is just an awesome perspective on life. For every student who benefits from learning an inspiring programming language, many students will benefit from learning that humanity has a method called mathematics for thinking about fundamental truths in a systematic, logical way that can capture things precisely. So any time I have to choose between pushing students further toward their math education or away from it, I’ll choose toward it.
- Details matter. Even though I know kids are capable of a lot, they are capable of a lot more without artificial obstacles in their way. I learned this the hard way teaching this class the first time. The smallest little things, with absolutely no great significance as a language, matter a lot. Having to put parentheses around negative numbers obscures students from reaching leaps of understanding. Confusing error messages mean the difference between a student who spends a weekend learning, and one who gives up on Friday afternoon and doesn’t think about it until the next school day. Different surface syntax means that a lot of kids never fully make the connection that functions here are the same thing as functions there.
In the end, I do think these were the right decisions… despite the frustration they can cause for Haskell programmers who know there’s a better way.
Making Up For It
A couple weekends ago, though, I worked on something to hopefully restore some of this loss for Haskellers. You see, all the changes I’ve made, in the end, come from replacing the Prelude module with my own alternative. Specifically:
- I deliberately replaced functions from the Prelude with my modified versions.
- Because I provided an alternative Prelude, I had to hide the base package, which made it impossible to import things like Control.Monad. This was not a deliberate decision. It just happened.
So I fixed this. I added to the codeworld-base package re-exports of all of the modules from base. I renamed Prelude to HaskellPrelude in the process, so that it doesn’t conflict with my own Prelude. And finally, I added a new module, CodeWorld, that exports all the really new stuff from CodeWorld like pictures, colors, and the interpreters for pictures, animations, simulations, etc. The result is that you can now start your programs with the following:
import CodeWorld -- If you still want to do pictures, etc.
main = putStrLn "Hello, World"
At this point, you can write any Haskell you like! You aren’t even constrained to pure code, or safe code. (The exception: TemplateHaskell is still rejected, since the compiler runs on the server, so TH code would execute code on the server.)
Right now, the CodeWorld module still uses uncurried functions and other CodeWorld conventions like Number for numbers, etc. There’s no reason for this, and it’s something that I should probably change. Anyone want to send a pull request?