I spent the last week in Berlin for the 2019 International Conference on Functional Programming. While I was there, though, I also took some time to work on outstanding CodeWorld issues. Here are a few updates.
One of the earliest things students struggle with in CodeWorld is managing indentation of equations. The rule is simple enough: new equations must start at the left margin, and any line wraps are indented. But students still forget this rule often, and the error messages aren’t always easy to understand. This is one of the most common mistakes that students make in writing Haskell code, simply because there are so many chances to make it!
Instead of trying to improve the error messages, I decided to make the error harder to make in the first place. I’m most of the way there.
The new indent mode does what is needed, at least. When you press Enter in most situations where it’s known that you need to continue the current statement, such as if you’re in parentheses or have just typed an operator that needs another operand, the indent is automatic. When you finish an expression, the new line is no longer indented. If you start a line with an operator, the indent is added to make it part of the previous line. It even does the right thing when keywords force a layout context to end, so typing in or where will often drop some indent on the line so that the new expression starts in the right place.
Note that this is not a source beautifier. It doesn’t pay any attention to how to make the source code look good; only to adding indents where necessary to keep the compiler working. Tweaking the indent rules to look better is an open area for improvement. So is making the Tab and Backspace keys work more intuitively by skipping to indent markers. But I believe this will reduce student errors significantly as they get started.
After all, the best kind of error is the one you never make!
Cleaner holes, and other things
The second thing I did was clean up GHC error messages for the typed holes feature. Typed holes are a feature of GHC that lets you type a single underscore in place of any expression. When you do this, you get a compile warning that identifies the hole and tells you what type it has, and which defined variables could fit there.
Typed holes are a GHC feature, so they already worked in CodeWorld. But the messages printed were daunting, for several reasons. Among them:
- They included a list of “relevant bindings” that students would not understand.
- They contained long-winded explanatory text about arguments to pass to GHC to control the display of information.
- They included notation with visible type applications when suggesting polymorphic functions.
- They just triggered a lot of bad corner cases in CodeWorld error rewriting that mangled the messages a lot.
It took a lot of fiddling with regular expressions, but I’ve cleaned up these problems so that the message pretty much says what you care about most: there’s a hole that needs to be filled, and here are some things you can fill it with.
So why did I suddenly care? Actually, it has to do with an ICFP session I attended: a tutorial session on teaching functional programming led by Michael Sperber. In the session, Michael talked a lot about how he asks his students to explicitly write “…” in their code as a placeholder for something that should be filled in later. The idea is to jolt students out of the habit of trying to put everything together in one pass and type the finished code, by instead asking (even requiring!) them to type fragments of incomplete code first, and then put it together later.
When doing this in Haskell, once could still type “…”, but it seems a bit of a waste when there’s already a language feature that’s just as good (instead of an ellipse, it’s a blank to be filled in), which will also guide the student to find an appropriate term. I am now interested in trying this out in teaching, to see if students will learn better when they are instructed to first put blanks where the function arguments go, and then type them again as a separate step.