viernes, 26 de julio de 2013

Why Haskell at School matters

Today I want to share a wonderful experience.

I'm professor of introductory programming in two years at Florentino Ameghino School and one at Universidad Nacional de Quilmes, Buenos Aires, Argentina.

This time I want to speak about my work at the school. It's my second year as a professor. Kids have 15-16 and 16-17 years old (4th and 5th year of secondary school, respectively) .

Last year, 4th year students had not programming experience. The same does not occur to the current 4th year, since they have played for a long time with Scratch when they were in 3rd year.

For both courses I started with a language called Gobstones, which we use at the university where I studied (and where I'm actually working). This programming language was created for educational purposes with some basic features of imperative programming, but designed with total purity (you can't modify parameters, no I/O operations, no global variables, etc.). There is a very clear separation between commands and expressions, and the same with procedures (that denote commands) and functions (that denote pure expressions). Aditionally, you can not watch intermediate steps or even debug a Gobstones program. There is only an entry point called Main, and you can only see the input board and the output board (the execution result of that procedure).

On the other hand, this language presents an universe of discourse that is simple but challenging: coloured stones, a board with cells that contains them, and a grabber at a given cell. You can represent many different things using only those elements.


The primitive values (and types) are:
  • Red, Blue, Green, Black (Color type)
  • North, South, East, West (Direction type)
  • Integers
  • Booleans

The primitive commands are:
  • PutStone(<color>)
  • TakeStone(<color>)
  • MoveTo(<direction>)
The control structures are:
  • repeatWith <index> in <firstValue> .. <lastValue>
  • while(<boolean>) { <commands> }
  • if(<boolean>) { <commands> } else { <commands> }
And there are definitions of new procedures and functions:

procedure <procedureName>(<params>)
{ <commands> }

function <functionName>(<params>)

   <commands>
   return(<expression>)
}

To resume this introduction to Gobstones, here is a simple program that counts how many cells are there in the column and uses this number to put blue stones:

procedure PutNStones(n, color)
{
  repeatWith i in 1 .. n
    { PutStone(color) }
}

procedure GoToEdge(dir)
{
    while(canMoveTo(dir))
     { MoveTo(dir) }
}

function countCellsOfCurrentColumn()
{
    GoToEdge(South)
    result := 1
    while(canMoveTo(North))
    { 
        MoveTo(North)
        result := result + 1
    }
    return(result)
}

procedure Main()
{
    PutNStones(countCellsOfCurrentColumn(), Blue)
}

"Can I call procedures inside functions?", the answer is Yes. But the global state before and after you call a function remains the same. The effects that occur inside a function are undone when the function returns a value.

Gobstones sintax is C-like, so students don't have difficulties when they have to work on general purpose languages. They can learn many useful things like divide and conquer, parametrization, etc.

Last year, I spent eight months teaching basic programming concepts with Gobstones. This year, since children had already used Scratch, I spent only three months to teach the same concepts.

In the 4th year the results are:
  • Last year I had eighteen students. Only one was enthusiastic about programming. Four of them learned a lot, and they did the exercises just well. The rest were completely bored and did not want to participate too much.
  • This year I have fifteen students. They do a very excellent job. Only four of them are lazy, but the rest is quite well. Some are extremely brillant. All students get inspired because they're truly learning programming concepts, in contrast to Scratch where they learned only to write a lot of code without understanding their programs and the ideas behind them. However, Scratch is an excellent approach to make motivating programming projects at first, and it have good results.
In 5th year I started with Python and I proposed proyects to create games. We use a very easy framework, and they have designed very original games. Some students participate a lot, but many of them are still bored. Nobody learned significant concepts, because they produce a lot of code what they cannot reason too much about it.

My concerns, like other educators, are:
  • Why some (or most) children doesn't like programming?
  • What kind of students are successful at learning programming?
  • Who of them might have difficulties to learn this discipline?
  • How can we motivate students at this age to learn programming?
My conclusion so far was "Many are not interested in programming, I can do my best effort, but they will not learn more than any basic concepts".

Thankfully that was not the end.

I learned to program like those children, using Gobstones to learn the basic concepts of structured programming. Then, I met Haskell and many others. But only "Haskell" has expanded my knowledge about math and programming languages until limits never imagined. Now I'm studying concepts about category theory, and even I'm reading the HOTT Book. But why Haskell encouraged this? Have I got some special skills to understand abstract concepts and reason about the code I write (see properties in it)?

I don't know these answers either, but I have switched the approach in both courses: we are using Haskell now. We are focusing on functions, types, algebraic data types, lists, recursion schemes, etc.

The results are extraordinary:
  • A lot of apathetic students awakened from their lethargy. Now they are becoming very participatory, and they are answering very complex questions, sometimes even better than other more dedicated students.
  • Most students can infer very abstract solutions and model their ideas easily.
  • All of them agree that imperative programming was more difficult, and many are amazed that Haskell is a programming language.
When I presented Haskell at first time in both groups, they were making conclusions in only two hours about these topics:
  • Functions, expressions and types (they answer questions about the type of functions and expressions)
  • Parametric polymorphism and polymorphic functions
  • Currification
  • Sum and product types
  • Pattern matching.
  • Partiality and the use of Maybe.
On second class with 5th year (another two hours) they have learned:
  • Lists, their constructors and operations (head, tail, null, etc.). They infered the types of all the functions and see the correspondence of what they do based on the types.
  • Map, filter and fold, comparing them with iterative implementations in Python. They infered the types of the paremeter functions. One girl understood the difference between foldr and foldl, and she said "if the binary operation is associative the result is the same".
  • Finally, we discuss why the name "map" for the function map.
When the last class finished, one boy come to me and complained: "professor, when will we start learning challenging things in Haskell?". I had to explain that many programmers find very diffcult the concepts they are learning at the moment, but for them, for some reason, it was an extremely easy job.

My latest personal conclusions of what it is happening are the following:
  • They are very happy seeing an algebraic data type implementation of a Pokemon, since they note that programs have this kind of definitions and properties, in contrast with the drawings they see on screen that are only showy. They enjoy recognizing how abstract the code is, and most of them don't reach these conclusions when they program in imperative languages.
  • They don't enjoy making games and having fun at school, they want to learn relevant ideas that they can use in real and even complex situations. They want to be useful and give correct answers to challenging problems.
  • Haskell does not encourage you to think like a machine. Most people don't like it (and me neither). Many children have not a procedural mind, but they can abstract a lot, so we can take a very good aproximation exploiting this. They can learn operational concepts later, but they can't if they don't feel confortable with what they're programming and reasoning about their code. This is posible with the correct approach.
Finally, I want to share what they have done in both years.