Modern Thaumaturgy
One of my first experiences with computers was as a user of UNIX system. My dad was able to wrangle a Texas Instruments Silent 700 terminal from work and got access to the games that were available. From home at a whopping 300 baud, we played wumpus and adventure while burning up rolls of thermal paper. While learning the shell interface, I also started to learn how to write some shell scripts. Nothing fancy, mind you, but there was an intoxication in being able to take a language of magic words and use that very language to extends the vocabulary and functionality.
This was an incarnation of most of magic: study to learn the vocabulary and rules so that you can issue imperative commands and then go further so as to make your own new magic words that do new things. It helped that the vocabulary of UNIX is based on an imperative syntax and that many of the base commands are rather terse or cryptic.
The making of magic - something without a great deal of apparent physical substance that can, at its invocation, bring about a change - was and still is intoxicating to me. Further, the ability to change the rules of the magic and to make new ones is the real drug here. UNIX at its core has this in spades. For example, devices appear to be files and new devices can be created by making new files (well, it's more than that). Don't like the built-in command for displaying date and time, write a new one. Don't like where your shell looks for commands? Reconfigure it to look elsewhere or in a different order. Don't like your shell? Change it. Don't like any shell? Write your own.
Looking at C, you see the same elements of design in that I/O is not built into the language and if you don't like the Standard I/O functions (and don't mind losing some portability), you can write your own. Writing your own version of printf is great exercise as long as you don't mind having to suffer through varargs.
Doing all of these things (and I have) is great experience and a great teacher as well for good design. Writing a class hierarchy is not significantly different in that every step of the way has to come with questions of what should be configurable, replaceable, parameterizable. Given those choices, what mechanism should be used to best implement that and what is the cost?
As another tangent, I think it's interesting to watch someone who is really comfortable in a GUI heavy application. If writing code and using command-line incantations is wizardry, then a truly competent application user is doing prestidigitation. There is no less magic in the wiggling of a mouse to make things happen.
If you want to try some of these things out without having to fiddle around with UNIX, you might consider writing a chunk of code based around the read/eval/print loop model.
This Wikipedia article is a little more Lisp/Scheme based than it needs to be, but the approach is still sound:
- Read input
- Evaluate it
- Print the results
And this is pretty much the basis for writing a shell. Hint - in writing a shell, the hard part is eval, and in most cases the print portion will do nothing, as it is usually a side-effect of the eval in most shell commands.