I also have to add to the praise of Concepts, Techniques, and Models of Computer Programming. I remember in particular an exercise from that book where you were asked to write a simple parser in a particular paradigm. It basically asked you to write the procedure parse(code, syntax_tree), where you'd give the code as the first parameter, and where syntax_tree was the output parameter. Once you had made that, the last sub-exercise said "Oh BTW, try calling your function with an empty code parameter, and an existing syntax tree." Lo and behold, it worked! My parser could run in reverse, and it spat out all strings that would produce the given syntax tree. This was a mind blowing experience at the time.