I have 7 teachers in my immediate family and a step-mother that is teaching literacy and computer skills in Africa. Personally I have taught computer classes for seniors, and recently volunteered as a Ruby mentor with Ladies Learning Code in addition to sharing what I have learned with friends and loved ones. Teaching is a pretty big part of my world and consequently teaching programming has been on my mind a fair bit.
Lately I have started building some tools to support the ideas I have been rolling around and thought I would sketch out a rough lesson plan for someone’s first experience with Ruby that connects the ideas to the tools. I am putting it up here in hopes of getting some feedback and that it might be helpful to others.
Two things I have read really shaped my thinking about how learning happens and therefore how teaching should be done. The first is a brilliant article called The Cognitive style of Unix. The insight there is that “helpful” GUI software actually interferes with the process of internalizing the set of rules that govern the task at hand.
The second is this paragraph from Nicolas Carr’s book The Shallows:
Purely mental activity can also alter our neural circuitry, sometimes in far-reaching ways. In the late 1990s, a group of British researchers scanned the brains of sixteen London cab drivers who had between two and forty-two years of experience behind the wheel.
When they compared the scans with those of a control group, they found that the taxi drivers’ posterior hippocampus, a part of the brain that plays a key role in storing and manipulating spatial representations of a person’s surroundings, was much larger than normal.
Moreover, the longer a cab driver had been on the job, the larger his posterior hippocampus tended to be. The researchers also discovered that a portion of the drivers’ anterior hippocampus was smaller than average, apparently a result of the need to accommodate the enlargement of the posterior area.
Further tests indicated that the shrinking of the anterior hippocampus might have reduced the cabbies’ aptitude for certain other memorization tasks. The constant spatial processing required to navigate London’s intricate road system, the researchers concluded, is “associated with a relative redistribution of gray matter in the hippocampus.”
What I am seeing in this paragraph is that quality of explanation only counts for so much, learning is mostly a biological process. We are not waiting for a “Eureka!” moment, we are stimulating the growth of new biological material. That takes time and repetition.
With those things in mind, it seems to me that the goal when trying to teach something like Ruby is to explain as clearly as possible the rules that govern that world with plenty of opportunities for repetition. A key part of making repetition happen aiming to make the learner autonomous as quickly as possible.
Theory is great but what would implementation look like? From my experience it seems there are two distinct “worlds” whose rules must be internalized for someone to start feeling comfortable with Ruby, the Unix command line and Ruby itself.
Learning the command line:
Have given a few people their first look at a command line, I have noticed that it is difficult for them to understand what it is they are looking at. They are thrust into a black and white world (aubergine and white if you are on Ubuntu) with no indication of what to do next. Relevant terminology to define at this point would be path, command/program and probably variable. Then you can focus on the rules that govern this world; the command line equivalent of physics. First it seems to need to understand what running a program looks like since that is the root of everything:
Following that general structure offers a template that learners should be able use to understand things like:
cd cat ~/Documents/foo.txt ls -al grep -ri kittens .
The other rule that shapes this world is that all programs are searched for, and hopefully found, in one of the folders that is contained (in a comma separated list) in a variable called $PATH. Even though the echo command is unfamiliar, learners should have some intuition about the meaning of:
echo $PATH
A few experiments with the “which” command (ex: which ls) should go a long way to helping internalize the rules of this world and build a set of expectations that will help learners understand output like:
mike@sleepycat:~☺ blah No command 'blah' found, did you mean: Command 'blam' from package 'blam' (universe) blah: command not found
All of this information is really just building towards allowing learners to understand the following statements:
mike@sleepycat:~☺ ruby -e "puts 'hello world'" mike@sleepycat:~☺ ruby ./hello_world.rb
With that we have a toehold in a new world and can work on understanding the rules that govern that.
Learning Ruby:
Teaching beginners Ruby is tough because you are constantly walking a fine line between providing accurate information and glossing over overwhelming detail. This part is a little less clearly defined, partly because of the complexity, partly because I am still exploring the ideas. One thing is clear to me though: because everything is an object in Ruby there is no way to duck the difficult topic of classes and objects. You have to deal with it pretty much right away.
Unfortunately it’s fairly common to start with something like this:
I say unfortunately because it has already raced past the basics physics of the Ruby world: on every line you will see some variation of thing dot action.
___________.__________()
Once learners have taken a long look at that basic structure then they should be able cope a little better with looking at an actual line of Ruby. Pressing home that point we could now use that dog image to highlight the basic structure: .new(), .look_alert() and .sit() are all behaviours known as methods attached to the object on the left of the dot. Things on the left of a dot are either a blueprint or an thing made from a blueprint.
The seeming exceptions to this rule where behaviour seems to happening without an object (think puts and all the other Kernel methods included in the main object) are because we are inside an object and that object (aka self) is the one understood to be receiving that call to action. Most likely it is preferable to opt for specifying things like puts
in its more verbose form Kernel.puts
.
With that information learners should be able to make sense of the following examples:
mike@sleepycat:~☺ ruby -e "Kernel.puts 'hello world'" mike@sleepycat:~☺ ruby -e "Kernel.loop {Kernel.puts 'hello world'}" mike@sleepycat:~☺ ruby -e "Kernel.puts(Kernel.eval('1 + 1'))" mike@sleepycat:~☺ ruby -e "Kernel.puts(Kernel.gets())"
Note that all of those are run with the Ruby command to reinforce the earlier learning of the command line. However after each command is run we are kicked rudely out of the world of Ruby but if we combine them we can stay inside it:
mike@sleepycat:~☺ ruby -e "loop { puts(eval(gets())) }"
That little bit of genius (which I have written about before) is taken directly from Josh Cheek and is a brilliant way to demystify what is often (unfortunately) the peoples first step into Ruby, playing with IRB. Now with a little grounding learners should be able to make sense of what they see there.
Exploring further:
IRB suffers the same problem as the command line: there is no obvious way to discover what to do. There is tonnes of information that would be useful to building a clear mental model of how the world of Ruby works but none of it is readily accessible to beginners. I decided to take a stab at fixing this by creating a gem called explore_rb.
It adds methods to the main object to display all available classes, all objects in memory and a few other things. Since being dropped into IRB is a lot like being shut in a darkened room, those methods allow you to explore the contents of the room. For a description of its methods you can see the readme.
We can use explore_rb to show the differences between classes and objects (the difference between a blueprint and something made using that blueprint):
mike@sleepycat:~☺ irb irb(main):001:0> require 'explore_rb' Use the following commands to look around: classes, objects, get_objects, symbols, local_variables, gems, draw_this, help => true irb(main):002:0> classes.include? :Cat => false irb(main):003:0> get_objects Cat NameError: uninitialized constant Cat from (irb):4 from /home/mike/.rbenv/versions/2.0.0-p0/bin/irb:12:in `<main>' irb(main):004:0> class Cat; end => nil irb(main):005:0> classes.include? :Cat => true irb(main):006:0> get_objects Cat => [] irb(main):007:0> Cat.new => #<Cat:0x007f8c411c49c0> irb(main):008:0> get_objects Cat => [#<Cat:0x007f8c411c49c0>] irb(main):009:0> Cat.new => #<Cat:0x007f8c411cf870> irb(main):010:0> get_objects Cat => [#<Cat:0x007f8c411c49c0>, #<Cat:0x007f8c411cf870>]
Additionally explore_rb can help visualize the execution of our programs. This is a big help when explaining functions/methods and return values. An example would be:
irb(main):022:0> class Cat irb(main):023:1> def initialize name irb(main):024:2> @name = name irb(main):025:2> end irb(main):026:1> def name irb(main):027:2> @name irb(main):028:2> end irb(main):029:1> def vocalize irb(main):030:2> "#{name} says miaow" irb(main):031:2> end irb(main):032:1> end => nil irb(main):033:0> cat1 = Cat.new("Chatoune") => #<Cat:0x007f39f74c1818 @name="Chatoune"> irb(main):034:0> draw_this { cat1.vocalize } File saved in /home/mike/Desktop. => nil
Which gives us the following image:
Now we have a visual showing the interpreter moving in and out of our methods. You can see it will drop into a method to process the instructions within and return from the method.
We can see it drop into the vocalize method, and then have to drop down into the name method to carry out the instructions there before returning to complete the sentence that vocalize is supposed to return.
At this point I think things get more free form and as long as the emphasis is on building up a solid set of expectations around how the world of Ruby works its really up to the mentor to read their audience and give them what they need. Ruby is a great language in terms of consistancy since it was designed from the ground up to be object oriented, rather than having object orientation grafted awkwardly on later, so that should really help the learning process.
While there are exceptions and caveats for everything I have said above I think exposing people to exceptions before they have internalized the rule can only damage the learning process. Where exceptions to the rules are revealed through experimentation (“why doesn’t ‘which cd’ return anything?”, “if I create objects with .new and 1 is an object why can’t I say Fixnum.new(1)?”) emphasising the what expectations still hold true is probably the most important thing you can do. It takes a lot of learning before you can appreciate stuff like this.
If you have something that has really helped your learning process let me know in the comments.