Exercises and examples from chapter one of Turtle Geometry by Abelson and diSessa implemented in javascript. Kinda blown away by this book: only on chapter one and this page already offers a pretty fun playground for exploring geometry and rudimentary graphics programming.
Click some of the links below for examples and inspiration. You can also type commands into the form below the turtle's sandbox. Commands you type will be saved in the history below the sandbox.
Pen controls include penup, pendown,
pensize pixels, and pencolor
color. Any CSS color name or hex value will work
for color:
Wikipedia: Web Colors
Have a look at the source code for these exercises if you like: tg.js
A quick refresher, mainly for those who may land here first instead of on part 1. I spent all of last Friday figuring out a failing capybara test around browser cookies. First, the tests:
describe "ServiceRequestController", type: :request do
it "redirects when credentials are missing" do
get new_service_request_path
response.status.should == 302
end
it "renders the form when credentials are found" do
page.driver.browser.set_cookie 'username=jhendrix'
get new_service_request_path
response.status.should == 200
end
end
Second, the controller:
class ServiceRequestController < ApplicationController
def new
user = LegacyUser.find_by_username(request.cookies['username'])
if !user
redirect_to cookies_path
end
end
end
First test passes, second test fails. The previous post traced
through line one of my failing spec:
page.driver.browser.set_cookie 'username=jhendrix'.
If you're here just for the work-around when testing with cookies in
Capybara with the Rack::Test driver and RSpec, here's the best
solution I've found so far to make it work. Nevermind trying to set
cookies via capybara. Just pass in the HTTP_COOKIE header with the
call to get.
describe "ServiceRequestController", type: :request do
...
it "renders the form when credentials are found" do
get new_service_request_path, {}, 'HTTP_COOKIE' => 'username=jhendrix'
response.status.should == 200
end
end
I still haven't answered my first question: "am I setting cookies
correctly?" As you can guess from the tl;dr, the short answer is
"No". But life is a journey, not a destination. I'll start again
with the first line:
page.driver.browser.set_cookie 'username=jhendrix'
The first place to look is at set_cookie and see if
I'm actually setting one. I'll start with the most important
programming power tools find and grep and
I'll add in another one: intentionally raising an exception.
find ${GEM_PATH//:/ /} -type f -exec grep -nH -e 'def set_cookie' {} /dev/null \;
Here's the results of that search, cleaned up for legibility and brevity
action_dispatch/http/response.rb:158: def set_cookie(key, value)
action_dispatch/middleware/session/cookie_store.rb:65: def set_cookie(env, session_id, cookie)
rack/response.rb:58: def set_cookie(key, value)
rack/session/abstract/id.rb:320: def set_cookie(env, headers, cookie)
rack/session/cookie.rb:121: def set_cookie(env, headers, cookie)
rack/mock_session.rb:23: def set_cookie(cookie, uri = nil)
As I mentioned in part 1, the hits in action_dispatch, rack/response and rack/session are probably all related to the real cookie code. That stuff has to be working correctly or no Rails app would be able to save cookies. It almost has to be something in rack/mock_session. But this time around I'll take one extra moment test my hypothesis. Look at the source for that method in Rack::MockSession :
def set_cookie(cookie, uri = nil)
cookie_jar.merge(cookie, uri)
end
Where's cookie_jar come from?
def cookie_jar
@cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
end
I'll take drastic action to confirm this is the code in question:
def cookie_jar
@cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
raise @cookiejar.inspect
end
Running the specs... sure enough they fail with a RuntimeError, for example:
1) new service requests redirects when credentials are missing
Failure/Error: get new_service_request_path
RuntimeError:
#<Rack::Test::CookieJar:0x00000103d592c0 @default_host="www.example.com", @cookies=[]>
# ./spec/requests/service_requests_controller_spec.rb:5:in `block (2 levels) in <top (required)>'
Now I know I'm going in the right direction. Is the cookie is getting set?
def set_cookie(cookie, uri = nil)
puts "Rack::MockSession.set_cookie(#{cookie})"
cookie_jar.merge(cookie, uri)
raise @cookiejar.inspect
end
...
def cookie_jar
@cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
end
.Rack::MockSession.set_cookie('username=jhendrix')
F
Failures:
1) new service requests renders the form when credentials are found
Failure/Error: page.driver.browser.set_cookie "username=jhendrix"
RuntimeError:
#<Rack::Test::CookieJar:0x000001015ee0f0 @default_host="www.example.com", @cookies=[#<Rack::Test::Cookie:0x000001015ee050 @default_host="www.example.com", @name_value_raw="username=jhendrix", @name="username", @value="jhendrix", @options={"domain"=>"www.example.com", "path"=>""}>]>
# ./spec/requests/service_requests_controller_spec.rb:10:in `block (2 levels) in <top (required)>'
set_cookie works: the cookie I've set in the spec shows
up in the CookieJar. Why is the spec failing? I'll
change the code to print out the cookie_jar instead of raising an
exception and keep searching.
def set_cookie(cookie, uri = nil)
result = cookie_jar.merge(cookie, uri)
puts "set_cookie('#{cookie}')\n#{@cookiejar.inspect}"
result
end
get new_service_request_path
My trusty friends find and grep will turn
up a long list. get is a really popular method name
for objects of every shape and size.
find ${GEM_PATH//:/ /} -type f -exec grep -nH -e 'def get(' {} /dev/null \;
I'll draw your attention to the punctuation... I'm searching for
get( and not just get so the results don't
include false leads like get_coffee. Cleaned up
results:
action_controller/test_case.rb:364: def get(action, parameters = nil, session = nil, flash = nil)
action_dispatch/routing/mapper.rb:476: def get(*args, &block)
action_dispatch/routing/route_set.rb:106: def get(name)
action_dispatch/testing/integration.rb:32: def get(path, parameters = nil, headers = nil)
action_view/flows.rb:12: def get(key)
action_view/flows.rb:46: def get(key)
active_model/errors.rb:93: def get(key)
active_record/identity_map.rb:77: def get(klass, primary_key)
active_resource/connection.rb:79: def get(path, headers = {})
active_resource/custom_methods.rb:56: def get(custom_method_name, options = {})
active_resource/custom_methods.rb:89: def get(method_name, options = {})
bundler/vendor/thor/actions/file_manipulation.rb:72: def get(source, *args, &block)
capybara/rack_test/driver.rb:75: def get(*args, &block)
ffi/struct.rb:40: def get(ptr)
ffi/struct.rb:51: def get(ptr)
ffi/struct.rb:66: def get(ptr)
rack/mock.rb:56: def get(uri, opts={})
rack/cache/appengine.rb:27: def get(key)
rack-cache-1.1/test/entitystore_test.rb:248: def get(key); self[key]; end;
rack-cache-1.1/test/metastore_test.rb:318: def get(key); self[key]; end;
rack-cache-1.1/test/spec_setup.rb:169: def get(stem, env={}, &b)
rack/test.rb:55: def get(uri, params = {}, env = {}, &block)
rake-0.9.2.2/doc/proto_rake.rdoc:70: def get(task_name)
rspec/matchers/operator_matcher.rb:15: def get(klass, operator)
sass/util/subset_map.rb:73: def get(set)
selenium/webdriver/common/driver.rb:105: def get(url)
selenium/webdriver/remote/bridge.rb:98: def get(url)
selenium/webdriver/support/event_firing_bridge.rb:20: def get(url)
thor-0.14.6/lib/thor/actions/file_manipulation.rb:72: def get(source, *args, &block)
I could use the same trick by methodically editing every one of
these results and raise exceptions in each and then run the specs
again. But that would be tedious and I have another trick to share.
It's particularly useful for any Domain Specific Language. I'll
just instrument the spec itself to report which get to
investigate.
it "renders the form when credentials are found" do
page.driver.browser.set_cookie "username=jhendrix"
puts "#{method(:get)}"
get new_service_request_path
response.status.should == 200
end
Running the spec...
#<Method: RSpec::Core::ExampleGroup::Nested_1(ActionDispatch::Integration::Runner)#get>
Connecting the dots between the earlier result
action_dispatch/testing/integration.rb and
ActionDispatch::Integration::Runner
It's not quite as easy to spot, but notice that get is
forwarded to integration_session
%w(get post put head delete cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
# reset the html_document variable, but only for new get/post calls
@html_document = nil unless method.in?(["cookies", "assigns"])
integration_session.__send__(method, *args).tap do
copy_session_variables!
end
end
end
Integration::Session is defined earlier in the same file:
class Session
DEFAULT_HOST = "www.example.com"
include Test::Unit::Assertions
include TestProcess, RequestHelpers, Assertions
There's no sign of any method named get in
Integration::Session, but looking through the included modules, I
found it in
RequestHelpers
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
which calls process which after a bunch of setup
creates a new Rack::Test::Session
def process(method, path, parameters = nil, rack_env = nil)
...
session = Rack::Test::Session.new(_mock_session)
which calls _mock_session
def _mock_session
@_mock_session ||= Rack::MockSession.new(@app, host)
end
That code looks familiar. Both the first and second lines of the spec ask Rack::MockSession for their cookies. Let's instrument that class a little further.
def cookie_jar
@cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
puts " Rack::MockSession.cookie_jar #{@cookie_jar}"
@cookie_jar
end
I also need to know when get is called. Back to
ActionDispatch::Integration::RequestHelpers
def get(path, parameters = nil, headers = nil)
puts "ActionDispatch::Integration::RequestHelpers.get #{path}"
process :get, path, parameters, headers
end
Run the failing spec.
Rack::MockSession.cookie_jar #<Rack::Test::CookieJar:0x00000103458068>
Rack::MockSession.cookie_jar #<Rack::Test::CookieJar:0x00000103458068>
Rack::MockSession.set_cookie('username=jhendrix') #<Rack::Test::CookieJar:0x00000103458068 @default_host="www.example.com", @cookies=[#<Rack::Test::Cookie:0x00000103457e88 @default_host="www.example.com", @name_value_raw="username=jhendrix", @name="username", @value="jhendrix", @options={"domain"=>"www.example.com", "path"=>""}>]>
#<Method: RSpec::Core::ExampleGroup::Nested_1(ActionDispatch::Integration::Runner)#get>
ActionDispatch::Integration::RequestHelpers.get /service_requests/new
Rack::MockSession.cookie_jar #<Rack::Test::CookieJar:0x00000102dce008>
Rack::MockSession.cookie_jar #<Rack::Test::CookieJar:0x00000102dce008>
The source of the problem is revealed in these results but it takes
careful reading to spot it. Inside MockSession,
cookie_jar is called twice. Then comes the output from
the call to set_cookie. Line 4 is from the spec where
I identified which get was called. Line 5 is the debug
output I just added. And notice that cookie_jar is
again called twice within the call to process.
All reasonable enough. Where's the bug?
Look very closely at the CookieJars in use. It'll help
if I put them right next to each other:
Rack::MockSession.cookie_jar #<Rack::Test::CookieJar:0x00000103458068>
Rack::MockSession.cookie_jar #<Rack::Test::CookieJar:0x00000102dce008>
Those may be the same class but they are different instances. The
call to get reads its cookies from a different
CookieJar than where they are set in the call to
set_cookie. In fact, those two calls even have
different MockSessions. Removing all the previous
debug output and adding this:
def initialize(app, default_host = Rack::Test::DEFAULT_HOST)
puts "#{self}"
@app = app
@after_request = []
@default_host = default_host
@last_request = nil
@last_response = nil
end
gives the following output when I run the spec again.
#<Rack::MockSession:0x00000102e69ad0>
#<Rack::MockSession:0x00000102d8e9d0>
My first work-around was this:
it "renders the form when credentials are found" do
page.driver.browser.set_cookie 'username=jhendrix'
page.driver.get new_service_request_path
response.status.should == 200
end
That does get those two lines of code using the same
MockSession which also makes the test pass:
#<Rack::MockSession:0x000001036e9db8>
.
Finished in 8.14 seconds
1 example, 0 failures
But in the end, that smells wrong to me. And having just looked at
ActionDispatch::Integration::RequestHelpers.get I had a
new idea for providing cookies:
it "renders the form when credentials are found" do
get new_service_request_path, {}, 'HTTP_COOKIE' => 'username=jhendrix'
response.status.should == 200
end
That test passes.
Beginner's Mind is really important. Don't be clever. Just carefully follow the code. Take notes along the way so you know the landmarks and can find your way back.
Raise exceptions in possible matches to confirm you're looking in
the right place. Replace the exceptions with puts
statements when you need to let the software proceed.
Ruby's built-in introspection, especially Object#method
can help identify methods in DSLs where you may not have an easy
handle on the object.
You have to read carefully. Read the code carefully and read debug output and stack traces carefully. Even when you narrow it down to the same class in similar contexts, you may be looking at different instances of the class.
It is reasonable to think that calling set_cookie
should work for the next get or post call.
It doesn't. page.driver.browser.set_cookie creates one
instance of Rack::MockSession and get
creates another. They're not the same session.
On the other hand, maybe it's not a bug in the code but in the documentation. If everyone knows to just pass in HTTP_COOKIE with the request, then there's no need for a pull request at all. I think that's where I've landed.
If for some reason you feel motivated to fix the interaction around
set_cookie, there's some kind of coordination needed
between Capybara's Rack::Test driver and the
Rack::Test::Methods so they can share the same
Rack::MockSession.
This morning Sarah and the kids made gingerbread cookies, thoroughly destroying all illusions of joyous family fun for the holidays. Speaking of cookies failing to deliver promises, I blew the entire day Friday figuring out a failing capybara test around browser cookies. Not a good weekend for cookies.
It's a Rails app which depends entirely on a legacy authentication and authorization system. The simplest thing that could possibly work is use cookies set by the existing authentication system. Should be simple.
describe "ServiceRequestController" do
it "redirects when credentials are missing" do
get new_service_request_path
response.status.should == 302
end
end
Test is red.
class ServiceRequestController < ApplicationController
def new
redirect_to cookies_path
end
end
Test is green.
describe "ServiceRequestController" do
...
it "renders the form when credentials are found" do
page.driver.browser.set_cookie 'username=jhendrix'
get new_service_request_path
response.status.should == 200
end
end
Test is red.
class ServiceRequestController < ApplicationController
def new
user = LegacyUser.find_by_username(request.cookies['username'])
if !user
redirect_to cookies_path
end
end
end
Test is red. I found a work-around just before lunch, but spent the rest of the day trying to understand why that doesn't work.
If you're here just for the work-around when testing with cookies in Capybara and RSpec, here's my first work around (but the better way to test cookies in Capybara, Rack::Test and RSpec is in part 2 ):
describe "ServiceRequestController" do
...
it "renders the form when credentials are found" do
page.driver.browser.set_cookie 'username=jhendrix'
page.driver.get new_service_request_path
response.status.should == 200
end
end
Use the page.driver when you call get.
What follows are some of the most important programming skills I learned working with Rob and Paul at bivio: how to find your way around other people's code. I'll start with the failing test.
describe "ServiceRequestController" do
...
it "renders form when credentials are found" do
page.driver.browser.set_cookie 'username=jhendrix'
get new_service_request_path
response.status.should == 200
end
end
First question: Am I calling set_cookie correctly?
There's a profoundly important tone in this question. Start with a
beginner's mind. Don't guess. Don't be clever. Just read and
follow the code. This is also one of the hardest things to do. As
programmers we're all pretty intoxicated with our own cleverness.
Let go and use The Source.
In RubyMine you can Command-Click on set_cookie or type Command-B.
That pulls up a list of likely candidates. Without RubyMine you
need some unix fu. Two of the most important, general purpose, and
language independent programming tools ever are find
and grep. Get to know them. They are your best
friends.
find ${GEM_PATH//:/ /} -type f -exec grep -nH -e 'def set_cookie' {} /dev/null \;
Either way you search, the results are similar (though not
identical). These are cleaned up from the results via
find and grep:
action_dispatch/http/response.rb:158: def set_cookie(key, value)
action_dispatch/middleware/session/cookie_store.rb:65: def set_cookie(env, session_id, cookie)
rack/response.rb:58: def set_cookie(key, value)
rack/session/abstract/id.rb:320: def set_cookie(env, headers, cookie)
rack/session/cookie.rb:121: def set_cookie(env, headers, cookie)
rack/mock_session.rb:23: def set_cookie(cookie, uri = nil)
The hits in action_dispatch, rack/response and rack/session are probably all related to the real cookie code. That stuff has to be working correctly or no Rails app would be able to save cookies. It almost has to be something in rack/mock_session.
There's a certain bit of irony here. I didn't trust these search results. I was Being Clever. I took a longer path and ended up in rack/mock_session anyway. The longer path did give me the confidence of a second opinion. Trusting The Source would have produced my answer more quickly. Then again, I might have found the answer and still not trusted it. I'll share the longer path I took which is pretty much what I could learn by stepping through with a debugger.
In this call, page.driver.browser.set_code where does
page come from? I know it's from Capybara, but where
exactly? It'll be in here somewhere:
https://github.com/jnicklas/capybara/tree/master/lib/capybara.
cucumber.rb
dsl.rb
rails.rb
rspec.rb
selector.rb
server.rb
session.rb
version.rb
Looking over that list dsl.rb looked most promising.
And sure enough in Capybara::DSL
I find
def page
Capybara.current_session
end
current_session is defined earlier in the file:
def current_session
session_pool["#{current_driver}:#{session_name}:#{app.object_id}"] ||= Capybara::Session.new(current_driver, app)
end
Now I know that page is a Capybara::Session using current_driver. What's current_driver? It's also defined in dsl.rb:
def current_driver
@current_driver || default_driver
end
And default_driver?
def default_driver
@default_driver || :rack_test
end
So page is a Capybara::Session
initalized with :rack_test. The relevant code:
attr_reader :mode, :app
def initialize(mode, app=nil)
@mode = mode
@app = app
end
def driver
@driver ||= begin
unless Capybara.drivers.has_key?(mode)
other_drivers = Capybara.drivers.keys.map { |key| key.inspect }
raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
end
Capybara.drivers[mode].call(app)
end
end
Remind me again what it was I was searching for? I've bounced
around the code enough to be lost in "a maze of twisty little
passages, all alike." Oh yeah:
page.driver.browser.set_cookie. After all this
caving, I'm finally through page and now looking
for driver. The good news is, it's right here:
Capybara.drivers[:rack_test].call(app). But where do
I find Capybara.drivers? Well given the name, let's look first at the top level capybara module: Capybara.
def drivers
@drivers ||= {}
end
Searching through that file, this is the only line that refers to
@drivers. What about references to
drivers itself? Here's the only one I could find:
def register_driver(name, &block)
drivers[name] = block
end
And register_driver?
Capybara.register_driver :rack_test do |app|
Capybara::RackTest::Driver.new(app)
end
Now I know the driver is a
Capybara::RackTest::Driver
and the top of that file invites the next question: what's the
browser?
attr_reader :app, :options
def initialize(app, options={})
raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
@app = app
@options = DEFAULT_OPTIONS.merge(options)
end
def browser
@browser ||= Capybara::RackTest::Browser.new(self)
end
Are we there yet? What was I looking for again? Oh, right. It's
set_cookie. But there's no sign of that method in
Capybara::RackTest::Browser.
But there is an important bit that's easy to miss right up top:
class Capybara::RackTest::Browser
include ::Rack::Test::Methods
Let's take a peek in Rack::Test::Methods.
set_cookie is delegated to the
current_session.
METHODS = [
:request,
:get,
:post,
:put,
:delete,
:options,
:head,
:follow_redirect!,
:header,
:set_cookie,
:clear_cookies,
:authorize,
:basic_authorize,
:digest_authorize,
:last_response,
:last_request
]
def_delegators :current_session, *METHODS
current_session points to rack_test_session:
def current_session # :nodoc:
rack_test_session(_current_session_names.last)
end
which points to build_rack_test_session:
def rack_test_session(name = :default) # :nodoc:
return build_rack_test_session(name) unless name
@_rack_test_sessions ||= {}
@_rack_test_sessions[name] ||= build_rack_test_session(name)
end
which creates a Rack::Test::Session with a rack_mock_session:
def build_rack_test_session(name) # :nodoc:
Rack::Test::Session.new(rack_mock_session(name))
end
which points to build_rack_mock_session:
def rack_mock_session(name = :default) # :nodoc:
return build_rack_mock_session unless name
@_rack_mock_sessions ||= {}
@_rack_mock_sessions[name] ||= build_rack_mock_session
end
"Which will bring us back to" Rack::MockSession.
def build_rack_mock_session # :nodoc:
Rack::MockSession.new(app)
end
And inside Rack::MockSession is where the test calls set_cookie.
def set_cookie(cookie, uri = nil)
cookie_jar.merge(cookie, uri)
end
And what, finally, is cookie_jar?
def cookie_jar
@cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
end
It's probably a newly minted Rack::Test::CookieJar. Now wouldn't it
have been a lot easier if I'd just trusted the search results in the
first place? find and grep are your best
friends. You can trust them to save you a lot of time.
Concentrations of Power. We take these truths to be self-evident: imbalance of power invites abuse of power, and power has tipped wildly out of balance. The evidence of abuse is widespread and mounting.
Of the People, By the People, and For the People. Abe Lincoln in the Gettysburg Address gave us this myth that our government was about the People. The actual institutions have always been a government of the elite. The Declaration of Independence opens with "We the People" but the Constitution divides power between State and Federal governments. The People are an afterthought squeezed into the Bill of Rights.
The S&P and Fortune 500s. The world's largest corporations have grown into stateless governments unto themselves. We the People have no representation in these corporations. Our only influence is how we work or how we spend. As wealth has concentrated into the hands off the few, the influence of the People over multi-national corporations has dwindled.
New Tools of Communication. We no longer need long chains of command and hierarchical organizations to organize and implement ambitious projects. We no longer need the talking heads in the media or political polls to tell us what the People think. We the People can speak for ourselves.
Emergence. We no longer need the singular vision of individual charismatic leaders to focus our collective action. The new tools of communication empower even very small groups to effect significant changes and for one small group to adopt and adapt the successes and failures of others.
We witness a shift of power. Power never shifts gracefully.
We the People are finally catching on that we can govern ourselves instead of ceeding power to Representatives and we can collectively relaim power ceeded to multi-national coroprations.
This is gonna be an interesting ride.
This one feels like one of those small-step-giant-leap things. I've written a simple interactive shell to play with turtle graphics in the browser. What's more, it works from my phone!
source code is here: https://github.com/dobbs/turtle
This makes me happy!
Commands include:
penup
pendown
move pixels
turn degrees
clear
Over Thanksgiving I was able to get a somewhat tested implementation of 2d turtle graphics in javascript -- the model is tested but the views that render into the canvas aren't really tests. The code is here:
http://github.com/dobbs/turtle
I was even able to re-create the von Koch snowflake code that was the starting point for this post:
http://dobbse.net/thinair/2008/12/logo-fractals-recursion.html
Here's the same fractals written in javascript with two implementations...
http://github.com/dobbs/turtle/blob/master/fractal.js
drawLine() is a reasonable port of the original logo code and fractalLine() is a generalization that lets me illustrate the similarity between the von Koch snowflake and the peano curve.
The drawings do not animate yet... they're just a different api over the HTML5 canvas. But I'm frankly blown away at the ridiculous simplicity of the implementation: move() calculates the change in x and y by just taking the sine and cosine of the turtle's direction! So on top of the way turtles enable people to play with differential geometry (incredibly high-level mathematics made trivial by effective use of computing), the implementation is a beautiful application of very simple trigonometry -- a unit circle at work inside a simple state machine. There are some exciting lessons around an amazingly small amount of code... both in the von Koch and peano example and inside the turtle itself.
I think my next steps will be to create a player that will animate the drawing step-by-step and then to enable in-browser editing of the scripts. No idea when I'll be able to make time for either of those little projects. But this feels like a nice start.
Oh, and if you happen to be visiting here with a browser that supports the HTML5 canvas element, you can see the von Koch snowflake and Peano curves illustrated below using my javascript turtle graphics.
My life's work is computational education : the systemic transformation of education as we know it through effective use of computing and networking technology. Systemic reform demands system-wide and system-deep solutions: no single area will be sufficient and all areas need attention. Although I have ideas for every dimension of the system, I'll focus here on a few specific areas where I would like to start now: ubiquitous computing, spatial reasoning, and programming.
Computing first stormed the gates of education back in the 1970's when the price of computers went down from 100s or 10s of thousands of dollars to only thousands of dollars. The widespread adoption of Apple II computers in school launched a few generations of computer geeks in their careers and thereby laid a foundation for the transformational changes we've seen in the decades since. But sadly education remained mostly unchanged for decades. We're in the midst of another drop in the cost of computing by another order of magnitude. Changes by an order of magnitude are important. There's a new gold rush going after mobile computing but I think the more important concept is ubiquity. Most people can now afford a powerful networked computer that fits in their pocket and turns on instantly -- access to all the world's knowledge wherever there's a wireless signal. But unless we learn from history, it'll be a decade or more before ubiquitous computing changes anything in education.
We can soon provide every educator and every child with their own mobile computer. What will we do with them? I'd like to be inventing the answers to that question because the best way to predict the future is to invent it.
Here are a few examples of educational opportunities I see in mobile computing: distributed computing, messaging, lessons in emergence, agents, artificial life, digital collection of experimental data in the field by capturing images, sound, and video.
I'm particularly excited about things we can do with computers that cannot be done in any other media. Three-dimensional modeling and representation is something that's been prohibitively difficult and expensive to do on paper. Time is also challenging to model on paper. The computer quite literally can take us to new dimensions in communication and creative expression.
Few people know or appreciate that the design professions (architecture, urban planning, landscape design) also offer excellent preparation for software design. With advances brought by computing, sophisticated spatial reasoning need no longer be confined to design professionals (and sculptors ;-). Anyone can build interactive pictures which communicate in both space and time. That's a paradigm shift (and I don't use that phrase lightly).
For an example of untapped potential in spatial modeling of knowledge, see my daydream of modeling the Map Room at St. Peter's Basillica There are a number of other examples to be found in the history of perspective. Someday (hopefully soon) I'll be able to recreate the perspective animations I created earlier in my life. Also, Google Maps and it's competitors invite home grown GIS applications to connect spatial data to maps where the only cost is the time needed to create the mashups. For a particularly inspiring example of spatial reasoning in action, take seven minutes to watch Blaise Aguera y Arcas demos Photosynth
Consider also the possibilities for applying Sketch-up and sketchyphysics and stereoscopic projectors... there's a whole lot of exciting and largely untapped material for education.
The relevance of this takes a little explaining, especially for non-programmers, but probably even for programmers.
The printing press completely changed the economics of books. With that change came widespread literacy to the point now where people are embarrassed if they cannot read or write. Programming is to computers what literacy is to books. To really harness the power of computing as a transformational medium, we need widespread computational literacy. This metaphor of computers to books is important. Throughout European history, power was concentrated in the hands of the very few people who were literate. The printing press and widespread literacy completely changed the balance of power. We currently live in an age where the power of computing can only be harnessed by the few who can master programming languages. Google and Apple and Microsoft, and also Yahoo and Facebook and others hold some incredible power for their ability to pay programmers to harness computing and networking. All of their customers benefit, but are also somewhat controlled by the design choices of the software on offer. Concentrations of power invite abuse of power and these companies are collecting a lot of power.
Programming languages are also powerful bridges between language and mathematics. Superficially, they look a lot like algebra and pre-calculus. However, much of programming is the art of choosing the right names and categories and the right abstract concepts to organize a large body of software. Although it's rarely thought of as such, programming is also language art. On a related note, to belabor the point, programming language communities display cultural dynamics just as natural language communities do. Perl culture is distinct from Python culture which is distinct again from the cultures of JavaScript, C++, Java, Objective-C, Lisp and Haskell.
Programming need not be confined to a narrow community of computer hackers. We can and should teach these skills more broadly. Alan Kay's keynote speeches over the past few years have pointed at a particularly exciting example. I'm also quite excited by the work of Open World Learning, and Scalable Game Design in demonstrating the potential to teach programming skills broadly.
WebGL
Processing and jython (especially the last section of that page entitled "Why?".
Android and jython, jruby, clojure, or other dynamic language environment
I'm thoroughly committed to test-driven development and have about a decade of experience writing code in this way. I just think about solving problems with software and automation and a large portion of my work will naturally include software and systems engineering.
I'm also a gifted teacher and a life-long learner. I enjoy stretching my own knowledge and skill and care a great deal about sharing what I've learned and helping other people grow. Teaching, mentoring, coaching, and pair-programming would all fit in this area. I would also enjoy and likely excel at instructional design.
I would also welcome the opportunity to collaborate with schools of education to help prepare future educators for a world of ubiquitous computing. The same tools I envision for students could well serve to prepare future educators too.
It seems I've started down the road of systemic education reform which for me is focused on effective use of technology for learning and teaching. This is a rough map of what I'll be working on for the rest of my life. Ideas range from design of instructional units and lesson plans, a whole branch of lessons from Alan Kay presentations, professional development for teachers, a collection of really big ideas that computation and networking enable, among other things. If I have seemed like I'm all over the map when speaking to you about educational technology, the "systemic" part might explain the appearance of a lack of focus. No one of these things is going to bring the technology revolution to education.
computational-education.pdf
computational-education.svg

I'm rethinking the design of my blog visually, organizationally, and in instrumentation. Well, I'm also rethinking my career. Hoping to focus my professional attention on computational education. Those of you reading via the feeds, there's likely to be some churn as I rearrange things a bit. I'll try to minimize the noise, but just in case I break something badly, you'll have had a little warning.
Computational thinking and computational doing are powerful tools for teaching powerful ideas.
In Makers vs. Sponges Elizabeth Corcoran comments via O'Reilly Radar:
I keep wondering why we lump all "technology" into the same basket. By doing so, we ignore the most important distinction of all: whether we are sponges for absorbing other people's ideas, or whether we're making our own.
Some of the comments on that article turned into a small discussion about tools vs. content. I composed this first in reply to that discussion, but wanted to expand on the idea here.
The article Gary S. Stager wrote: A new paradigm for evaluating the learning potential of an ed tech activity emphasizes a need for education to introduce "powerful ideas". I'd like to expand on that thought with a few concrete examples.
Once upon a time, a very long time ago, long division was the subject of doctoral dissertations. Today long division is taught in elementary school.
What changed between those eras was the introduction of hindu-arabic numerals. Long division in roman numerals is profoundly difficult. The new technology of digits, and especially the humble number zero, completely changed long division into the relatively simple algorithm that we all learned in grade school.
The technological revolution that I want to see in education would make physics and calculus and linear algebra accessible to elementary aged kids, among other advanced subjects. But current student assessments are measuring for things like long division -- more advanced subjects would be missed completely. And schools of education are not preparing future teachers to teach more advanced material.
When I talk about teaching physics in elementary school many people look at me like I'm insane, or that I have no concept of "age appropriate". But the future is already here. It's just not very evenly distributed.
In 1967 Seymore Papert was introducing elementary aged kids to LOGO. The turtle graphics in logo are differential geometry -- very advanced mathematics made completely accessible to young children. Some of Alan Kay's recent work includes fifth-graders recreating Galileo's experiments and then building computer models of gravity to compare with their experimental data. That's physics -- newtonian mechanics to be specific -- made accessible to grade schoolers.
Forty-three (seriously? 43?!) years later we don't see even LOGO among educational standards. I don't think there are enough adults who understand what a huge leap it is to teach differential geometry to kids through turtle graphics. In the late 1970s Apple II computers poured into many schools and LOGO became widely available in education. But those thirty plus years ago a bunch of adults saw some pretty pictures, shrugged, and ignored it as child's play instead of recognizing it for the little revolution it really could be. What else could we be teaching with these tools? If we could start elementary kids with an elementary understanding of newtonian mechanics, what could we be teaching them by the time they got to high school?
I really like Gary's comments about powerful ideas and can't agree forcefully enough that computational thinking and computational doing are powerful tools for teaching powerful ideas. It is about the tools, but not only about the tools. Like the humble number zero, computing can completely transform the nature of the material we would like to teach our children.
Other posts about powerful technology and powerful ideas:
Alan Kay and computational thinking
Logo, Fractals, and Recursion; Programming, and Removing Repetition