Beginners’ Programming Environments, and Haskell
Greenfoot is a beginner’s programming environment for ages 13 upwards. It is a programming framework and IDE for Java targeted towards games and simulations: anything where the main output is 2D graphics and sound. Here’s some screenshots:
Greenfoot has several nice aspects (disclaimer: I’m a developer on the Greenfoot project). One is that the visual setting allows instant feedback on what your code is doing. You write a line of code to get your crab moving across the screen, and two clicks later your code is running and the crab is moving around. This sort of instant and obvious feedback is very useful when you are beginning programming. (Actually, it’s always useful — but we have to learn to do without as we progress to programming more complicated systems!) You can invoke methods on objects by right-clicking on the object and selecting the method from a pop-up menu, which is a little bit like a graphical version of a REPL. So if you are given a scenario with some pre-coded behaviour, you can interact with the objects before even getting to the code.
Another nice aspect of Greenfoot is that it embodies object-orientation well. Not everyone (and probably especially not Haskell programmers) is sold on OOP, but set that aside for this post. The object/class divide is immediately present: Wombat is a class, you can create many Wombat objects in the world that have the same fields and same behaviour, but different state. This is obvious from them running around separately: the concept comes across implicitly rather than having to be taught too explicitly, because of the design of the system.
A while ago people used to scoff at Java, declaring “public static void main(String args)” as a reason to stay away: too much is revealed upfront before the students can start programming. Incidentally, one could equally apply the same criticism to Haskell: the monadic type of the main function forces beginners to confront monads immediately. Greenfoot and other frameworks have shown this is easily dealt with. You will never see the main method in Greenfoot, and care is taken to steer beginners away from blank screens or from having to learn too much before they can get going. Writing Hello World starting with a blank file takes at least seven lines of Java code to print out a line of text, but adding one line of Java code in your first part-filled Greenfoot scenario makes a crab run across the screen. It isn’t the language that makes the difference for how hard it is to get started, it’s the framework.
The original designers of Greenfoot wanted to teach OOP to beginners, so they found a way to get across the key concepts of OOP easily, put it in a setting (interactive games) that can attract young beginners and produced the software (and supporting teaching material). Idea times execution.
What about Haskell?
This leads to my question: if a beginner’s environment can be produced for OOP and Java, surely it can be done for functional programming: specifically, Haskell. (The same question applies to the process-oriented programming model that CHP espouses — I believe the answer there is a clear yes, with a relatively close correspondence to the OOP design of Greenfoot, so it’s a bit less interesting to discuss.) Let’s toy with a potential design for a Greenfoot-like system in Haskell: that is, an interactive, graphics-based environment for beginner Haskell programmers. I’ll give our hypothetical system the name StartHaskell, just to make referring to it easier for the rest of the post.
First of all, what are the key concepts we would like people to learn in Haskell at the beginning? Function calls are central: without understanding functions and function calls, you can’t do anything in Haskell. Types are quite important, too — especially since they are bound to be the second class of error you encounter (your first will be a syntax error). These are very obvious: functions are as fundamental to functional programming as variables are to imperative programming, and types are very important in functional programming.
A key and distinguishing feature of Haskell is the notion of a monad. Different people have different opinions on monads. Is it best to introduce them via a simple monad like Maybe or State, or is it better to use the IO monad? A myriad of monad tutorials on the Internet provide all the different answers you can imagine. The IO monad is the one that does tend to push monads in the programmer’s path sooner rather than later: the main function has a monadic IO type. Should this be hidden by providing a main function that calls the programmer’s pure functions, or should it just be exposed straight away? Whether or not you expose monads is closely tied to the API design of StartHaskell.
The notion of API in a programming environment like Greenfoot or StartHaskell actually swings both ways. There is the API that the StartHaskell system provides to the programmer, which will include things like functions dealing with images. But there is also the API that the programmer implements for the StartHaskell system — which is mainly what I want to discuss (and which has a heavy influence on the first kind, the system-provided API). In Greenfoot, every active thing in the world inherits from an Actor super-class, which has an act() method that the programmer implements to provide the functionality of that actor. These act() methods are called once each frame by the Greenfoot system: this is the API that the programmer implements. Since our hypothetical StartHaskell system will be functional rather than object-oriented, we will likely have one function to provide the functionality for the whole system (called once per frame) rather than bundling the functionality with individual objects. So let’s explore some different designs.
Option #1: Avoid monads entirely
We could focus entirely on pure functions. Conal Elliott’s picture work comes to my mind — and Simon Thompson’s Haskell book uses pure picture-based examples from the outset (using lists of lists of pixels, rather than Conal’s more abstract approach). You could conceive of a graphical framework that perhaps used a function from input-set to output-picture, i.e. step :: (KeyboardInput, MouseInput) -> Picture that would then be called each frame to produce an output picture given the input. Persistent state — that is, state that persists between frames, such as the position of the player in the world — is needed, so you might allow state to be returned as a result and passed to the next invocation, i.e. step :: (KeyboardInput, MouseInput) -> a -> (Picture, a). You could then provide various combinators for composing pictures that the programmer can use. (An approach based on Functional Reactive Programming, aka FRP, might also be possible — I don’t know enough FRP to talk about this, but feel free to write your own blog post explaining how FRP is the way to go!)
Option #2: Have a pure/monad split
Kodu is a new, very polished system for young programmers that uses a visual approach based around “when/do” rules. The “when” part specifies a condition that constrains when the rule is run, and the “do” aspect specifies the actions to take when the condition is satisfied. This easily translates into an API such as: rule :: (Environment -> Bool, SomeMonad ()). Different rules could be combined via combinators, or by changing that API into a list of rules. (One problem I have with lists of these rule-based interfaces is precedence: when one rule fires, does that preclude all other eligible rules firing? Or do they all fire together — and if so, in what order?) The system could then provide functions for accessing the environment data-type, and various monadic actions for affecting the system, such as drawing. Persistent state would probably be handled by making the SomeMonad a state monad.
Option #3: Introduce monads immediately, using do-notation
A lot of people try to understand monads by looking at the type signatures and going from there. If you completely ignore how monads are implemented, and just look at do-notation, many monads (especially the IO monad) are very close to imperative programming, and thus this is one way to introduce them. Haskell is a very nice imperative language! In this case we might require the user to provide an API like step :: SomeMonad (), or to make persistent state more apparent, step :: a -> SomeMonad a. The system would then provide an API with monadic functions to ask about the world, and to affect the world.
Option #4: Don’t Call Me, I’ll Call You
In all my options above I have assumed that the StartHaskell system will call the programmer’s function each frame. This design is taken from Greenfoot, and has various benefits. One is that if the programmer’s function is empty, the surrounding system will still be rendering frames and receiving input. The interact function in Haskell has this same flavour: here’s a function that contains my desired behaviour, you handle the heavy lifting of doing the IO and call me at the appropriate point. One problem with being called by the system is state. A graphical interactive program such as those in Greenfoot (e.g. games) will tend to maintain state: the positions of things in the world and the state of the player. I mainly dealt with that above by allowing the programmer to return some state that will be passed back in next frame. I’ve waved my hand slightly over the type of the state: each program will have a different type for the state, but that shouldn’t be hard to deal with in the environment.
An alternative approach is to take control: I’ll write my own code to drive the program and will call you at the appropriate points. There could be a single call into the framework, like nextFrame :: SomeMonad (), that does everything necessary: waits until the next frame is due, renders it, reads all the input devices and so on. So the most basic program would be forever nextFrame. Not too heavy to get started. Then you could pass state around yourself more easily instead of having to return it to the StartHaskell environment to be passed back to you later.
I don’t know which of the above options is best — there may be other better options that I haven’t considered. What I do know is that it is surprisingly important. What kind of API you ask the programmer to implement will have a big effect on the style of programming that they learn, and on the concepts that they will have to master. This is particularly true in languages where multiple styles of programming are supported, like Haskell or C++, whereas Java only really has one main style of programming.
Greenfoot is not just a software library/framework for graphics. It is a complete programming environment, including interactive invocation, debugging, buttons for compiling and running the program (with no build scripts in sight), and a source code editor. Having a complete environment helps beginners a lot. You do not want beginners who you are teaching any programming language to learn the compiler command and flags on day one: that can come later, the point is to teach them programming! It is also important to offer a simple environment: giving Eclipse to someone who hasn’t programmed before is just unnecessarily complicating their learning process. A beginner’s environment should be simpler than that of a professional programmer.
I think one major obstacle in Haskell is likely to be its error messages, particularly those for types. (Is every error message in Haskell one of: a syntax error, a type error, or an unknown identifier?) There has been work done before, for example in Helium, to try to improve this for Haskell. A better approach to teaching functional programming as a whole might be to use a language with a less powerful type system that allows for simpler errors, but I’m constraining myself to use Haskell for this post. So if you do have to teach Haskell, how can you best help with type errors?
Here is a mock-up of something that I always thought would be useful for type errors:
The idea being that those call-outs appear one-by-one as you move through the source. I like it because it visualises the process that I go through in my head when I encounter a type error, and the computer can doubtless do it faster than me. However, it is always worth bearing in my mind that what is helpful for an expert (as I guess I am in Haskell) is not necessarily helpful for a beginner. You need some level of understanding of type inference to work with that representation, and if a beginner doesn’t have that understanding then the interface may only serve to confuse.
Thoughts on Teaching Programming
I am a beginner when it comes to teaching and educational research, but here’s a few thoughts I’ve picked up so far:
- Consider the following as a guiding principle. There are only two reasons that things are hard to teach: what you are trying to teach needs to be improved (e.g. it needs more research), or you haven’t found a good way to present it. (Warning: this is an unfalsifiable statement!) Recursion is meant to be hard, but there are many who believe recursion (particularly structural recursion) is easy when taught right. Objects were meant to be hard, but I think objects-first works quite well at getting OO concepts across. Concurrency can be taught to beginners when it’s a good model (occam in combination with robotics has proved easy to teach to beginners). And so on and so on.
- Beginners can benefit from tools that are specialised to beginners. It is helpful if both beginners and experts realise this! Beginners shouldn’t start with Eclipse because they’ve heard that’s what Real Developers(TM) use, and experts should not pour scorn on beginners’ tools because they are too simple or hide intricate detail — that’s the point!
None of what I have said here about teaching is new, and much of the advice here is already followed on many courses. Most teachers give out partially-complete code to avoid students beginning with a blank screen, and it’s well-known that the key to teaching is to find good examples and present the concepts in the right way: it’s much easier said than done! My main intention was to think about what an equivalent to Greenfoot for functional programming would be like. It may be that an interactive graphics-based setting is not a good fit for a first functional programming environment. Perhaps there are other alternative settings that could be used — but it I think it should be something with a more widely appealing end result than calculating the fibonacci numbers!