Thursday, October 22, 2009

The joys of onKeyDown

I recently found myself in need of a way to detect when a user presses enter when typing in a text input box and happened upon one of the quirkiest of browser features, key press detection in JavaScript. Searching the Internet provided a few ideas but nothing that quite fit what I was looking for, so I thought maybe I should get the word out about what I've learned.

Lets assume that we have a form with an input box and we want to perform some special action when the user presses the enter key while focus is on the text input box. We begin by adding an onKeyDown event to the input box (you might also consider onKeyUp, but onKeyPress seems like it might be somewhat quirkier).
<form>
<input type="text" id="my-input">
</form>
<div id="output"></div>
<script>
document.getElementById('my-input').onkeydown = function(e) {};
<script>
Now the first hurdle is that in IE, Safari, and Chrome you can access the ASCII character code for the key which was pressed in window.event.keyCode but in FireFox you'll need to get the event object and look at the which member.
<script>
document.getElementById('my-input').onkeydown = function(e) {
var keyCode = 0;
if (window && window.event && window.event.keyCode) {
keyCode = window.event.keyCode;
} else if (e && e.which) {
keyCode = e.which;
} else {
alert('fail');
}
document.getElementById('output').innerHTML = 'You pressed ' + keyCode;
};
<script>
Now there is one gotcha with what we've written so far. Each key that we press when focus is on the desired input causes our code to be run, but the browser also performs the default behavior for that key. If our input is a simple text box, the characters pressed will show up in the text box. If the input is part of a form with a submit button, pressing enter will cause the form to be submitted. If the form is submitted then the browser is sent to a different page which in this case is not what we want.

There are a few ways to prevent the key presses from also triggering the default behavior and do only our behavior. One of the simplest ways is to return false from the onKeyDown function. If we change our handler to the below, characters will not show up in a text input box because our handler consumes them.
 document.getElementById('my-input').onkeydown = function(e) {
var keyCode = 0;
if (window && window.event && window.event.keyCode) {
keyCode = window.event.keyCode;
} else if (e && e.which) {
keyCode = e.which;
} else {
alert('fail');
}
document.getElementById('output').innerHTML = 'You pressed ' + keyCode;
return false;
};
With the above, the last key press is displayed in the output, but in most cases we probably do want the user to see the chracters they've typed showing up in the input box. We just want to prevent the form submit when the user presses the enter key. To accomplish this behavior, we can return false when we don't want the key press to propoage and return true when the default behavior should also be performed.
 document.getElementById('my-input').onkeydown = function(e) {
var keyCode = 0;
if (window && window.event && window.event.keyCode) {
keyCode = window.event.keyCode;
} else if (e && e.which) {
keyCode = e.which;
} else {
alert('fail');
}
document.getElementById('output').innerHTML = 'You pressed ' + keyCode;
if (keyCode == 13) { // 13 is the key code for the enter key.
return false;
} else {
return true;
}
};
There you have it, a way to detect when the enter key is pressed in our input box while also preventing the form from being submitted.

Monday, September 28, 2009

Journaling

This evening I found my long lost journal. In dusting it off and cracking it open I learned that my last entry had been over a year ago. With the exception of this last year long hiatus, I've regularly kept a journal since sometime in 1999 or 2000. I've found writing my thoughts helps me crystallize them and I tend to deal with thoughts and emotions internally, mulling them for hours or days. This is one of the reasons I think I've stuck with blogging.

Keeping a private journal is a completely different feeling than keeping a blog. When I write here I'm intensely (sometimes even paralyzingly) conscious of my audience. Not that it's a large one. Here I tend to focus on software related discoveries and creations. It is one creative outlet, but one decidedly different from when I journal.

It was refreshing to scratch blank pages with pen, writing for an audience which understands me better than most.

Okay now how many of you, when you saw the title, thought that I was going to talk about a file system or database? :-) How about you, do you keep a private journal?

Wednesday, August 26, 2009

A Matrix Sim in JavaScript

Do you ever find yourself working on a large project with a long lead-in time before you have visible results to show? I had been frustrated by this feeling recently. I had been focusing on a personal project which will likely be a long time in the making, so I decided to pick up a short, simple, and quick project to keep things lively and fresh. So our story begins.

During high school, I was wowed by the movie The Matrix. The special effects were mind blowing (for their day) and the story line was something I could really get behind (not so with the sequels, but I digress). I imagine this is how my elders felt when Star Wars was first released.

One of the most memorable visuals in the movie is the code of the matrix zipping by vertically on the screen in a retro green on black. Cypher, makes a statement, which seems to be a favorite among programmers, that he no longer sees the code itself, but what it represents. He has become fluent to the point of transcendence - a geek nirvana experience.

At this point in my life I had written a few moderately complex programs, mostly using Turbo C, and I thought: "Why, I could make my computer look like the matrix." It was one of the more involved programs I had written at the time and I was quite pleased with the result. I brought it in to the computer lab at school, loaded it onto all of the machines, and had the whole lab running it at once. I'm pretty sure that Ben, Yed, and Roman G. remember it. Sadly I doubt the code would run on the DOS emulator that ships with Windows XP and higher. I recall vaguely that Roman ported it to Win32 or something in our high school computer science class. Now that I think about it, I guess you could say that this was my first open source experience.

In thinking of something quick and easy to create with some immediate visual payoff, I decided to recreate my little DOS matrix sim, but this time in the browser. Behold: The matrix in JavaScript.

It turns out that the CSS rules needed to keep columns at a fixed width and height was actually quite complex and took longer than expected to get right. The vertical line height was still elusive in Chrome and Safari, but worked in Firefox and Opera. I don't think that this works yet in Internet Explorer. Though if past traffic is any indication, none of you would have noticed had I not said something :-)

Many thanks to Scudmissile for his mighty CSS kung fu. Oh, and that whirring sound that you hear may be your CPU fan trying to take flight. Apparently rendering a vertical text flow like this using DOM manipulation, CSS, and Math.random may be a bit more processor intensive than my obsolete C code.

Saturday, August 01, 2009

A Test Client for App Engine

I created a simple utility library in Python to help anyone debug their App Engine application. Many of the App Engine apps that I've seen use HTTP, HTML form posts, and the Users API and the easiest way to test these features is to fire up the web browser and click through the web pages generated by the app. However this can be a bit slow and repetative and it is difficult to determine exactly what is being sent over the wire (though this is greatly helped by using wireshark, fiddler, tcpdump, or antoher network packet sniffing tool).

Enter my little App Engine HTTP module. It provides a simple interface for making arbitrary HTTP requests and will print the full request and response to the terminal (though you can turn the noisy printing off if you want). Download, copy the http.py file to you working directory and try it out in your Python interpreter.

For our first demonstration, let's try to visit the Google search page.
import http
client = http.Client()
resp = client.request('GET', 'http://www.google.com')
You should see your request and the server's response (with the HTML for the Google Search page) in your terminal window. This should work with just about any website out there.

Other HTTP debugging tools can show you the request and response like this, but I find that this kind of simple Python client can be useful in writing end-to-end or integration tests which contact your App Engine app remotely.

Along those lines, one of the things which standard HTTP debugging tools do not provide, is a way to sign in to an App Engine app with a Google Account so that the App Engine Users API can identify the current user. I wrote an extremely simple app which illustrates the Users API, try it out here:

http://jscudtest.appspot.com/user

After signing in, the page should simply say, "Hello yourusername (yourusername@yourdomain.com)" You'll notice that during the sign in process, you signed in on www.google.com/accounts and were asked to approve access to the app. This kind of interaction works great in a browser, but can be tricky when you are using a command line, browserless, client.

It is possible however, to sign in to an App Engine app without using a browser. You can use the same technique used in appcfg, use ClientLogin and use the authorization token to obtain an app specific cookie which indicates the current user. This simple HTTP library can do this for you and all subsequent requests will use this cookie to tell the App Engine app who the current user is. Try it out by making the request to the simple user app that you visited earlier:
import http
client = http.Client()
client.appengine_login('jscudtest')
resp = client.request('GET',
'http://jscudtest.appspot.com/user')
print resp.body
You should see the following text displayed in the terminal:
Hello, yourusername (yourusername@yourdomain.com)
You can use the appengine_login method with your own app, just change the argument to the App ID of the app you want to access.

Along with simplifying access to apps which use Google Accounts, I wanted this library to simplify the process of using another feature used by many web apps: HTML form posts. Now I'm certain you've used HTML forms before, here's a simple example:

http://shoutout.appspot.com/

The above app uses both the Users API and a simple form. As an alternative to visiting this page in the web browser, you can post your shout-out using the following:
import http
client = http.Client()
client.appengine_login('shoutout')
client.request('POST', 'http://shoutout.appspot.com/',
form_data={'who': raw_input('From: '),
'message': raw_input('Message: ')})
If you've even wondered what gets sent across the wire to post on a form like this, look back in your terminal to see the request from your computer and the response from the server (this is of course just the HTTP layer, wireshark will show you traffic on the IP and Ethernet layer as well).

That's really all there is to it. I designed this as just a simple script to use on the command line and I wrote it in less time than it's taken me to write this blog post about it (I borrowed atom.http_core from the gdata-python-client as a foundation). With some tweaks to remove the interactive (getpass and raw_input) calls and replace them with parameters, I could see this module as a utility layer in a larger, more complex, App Engine client application. If you're creating on I'd love to hear about it ;-)

For more information on how the appengine_login method works behind the scenes, see this presentation I gave a few months ago:



Many thanks to Trevor Johns and Nick Johnson for helping me to understand how this ClientLogin-to-cookie exchange works.

I'm sure that App Engine's Java runtime users would appreciate a port of this simple library to Java, if you feel so inclined.

Tuesday, July 14, 2009

New version of my Dirt Simple CMS

I have just uploaded "version 2" of the dirt simple content management system which I developed almost a year ago. For those who don't recall, scud-cms is an extremely thin layer on top of App Engine with a plain text box based editor for creating web pages. The only new feature in this release is the ability to page through all of the content in your app by visiting the /content_lister page. I implemented the pagination system using "key only" queries and order-by-key which are fairly recent features in App Engine. For more potential designs to page through datastore entities, see this article on pagination.

I was inpired to work on this in part by a comment from Jason Worley (swirleydude on twitter) who has been using it and appears to like it quite a bit. At some point I plan to do a version 3 release which will include file uploads (great for images) just as Jason has done in his own modifications. Having someone use your software, enjoy it, tweak it, and build on it is rewarding in a way which is quite unique.

Tuesday, July 07, 2009

Updating my OLPC XO laptop

For a few months my XO laptop was my primary personal machine. I've written before about some of my favorite features, and after a year and a half with it, my favorites are still:
  • A screen that is readable in full sunlight or glare
  • Linux
  • Long battery life
  • Runs absolutely silently, no fans or spinning hard drive
However, there were a few drawbacks. It is a bit short on ram and processing power but most of the time I'm using simple programming tools, so I don't need much. Web browsing on some sites was a bit slow though. The biggest pain point in my book was the lack of copy and paste capability. I often found myself needing to retype a lengthy command in the terminal. On other systems I would have just copied and pasted from the web browser. For a while, I worked around this by using screen, which has a built in copy-paste buffer.

It turns out, in a recent release, the OLPC team has assuaged my number one annoyance. It is now possible to copy text from any of the activities (programs) and paste into another (like the terminal). In order to use this new goodness, I needed to update my XO operating system from the current version (656) to a newer build (802).

The process of updating turned out to be quite simple. If you are undertaking these same steps, please note, this does wipe out some parts of the operating system. New programs which you've installed using yum or rpm's might be erased depending on where they are installed. It looks like everything in the /home/olpc directory is untouched. For complete instructions, see the laptop.org wiki page for olpc-update.

To update, open the terminal and become root by executing
su
After that, make sure that your laptop is plugged in to a source of electricity and connected to the internet as this next step involved downloading the new operating system and overwriting your existing OS. Probably not something you want to lose power during.

To download and install the new OS run
/usr/sbin/olpc-update 802
Once you run this your laptop will spend the next several minutes to hours downloading and installing. A few exciting status messages will appear on your screen. Things like "Deleting old pristine version" - yikes!

I've found 802 to be a significant improvement over 656. The copy paste functionality is great and the UI looks and feels cleaner. Selecting applications and switching between them is easier than it used to be. With copy and paste now firmly in place I plan to use my little green machine quite a bit more.

Thursday, June 25, 2009

Partial Function Invocation

My wife tells me that I often jump into an explanation by starting at the beginning of my train of thought without giving any indication of where I'm going. It would be better if I began with the point I'm trying to make, then explain how I reched my conclusion, How am I doing so far? Oh wait, right... Here is my conclusion:

Allowing a function to be partially invoked, to allow some of the arguments to be specified at different times, can allow for code which is more flexible than by just using objects or pure functions.

I've been thinking about this lately as I refactored the gdata-python-client which is a library which can be used with AtomPub services. I'll spare you all the gory details, and offer a simple example of how partial function invocation might come in handy.

When making a request to a remote server, you might need the following information, just for example: username, password. URL, message body, and content type. So we start out by writing a stateless function to take this information and open a connnecion to the server, format our inputs, transmit our request, and parse the response. We'll call it post, and using it looks like this:
serverResponse = post(url, data, contentType, username, 
password)
This is all well and good, but suppose the final request, as shown above, is preceeded by a whole series of function calls. Each function would need to dutifully pass along parts of the request. Say for example that the user types in their username and password long before the request is made, so these get passed as parameters to lots of functions which only receive them so they can pass them on. In addition, the username and password are almost always the same from request to request, so the same values are being passed to the post function over and over. In cases like this, we will often use an object to hold common values.
class Requestor {
username
password

method post(url, data, conteentType) {...}
}
Now our request will look like:
client = new Requestor(username, password)
serverResponse = client.post(url, data, contentType)
What we've effectively done here is specified some of the information in advance and left other pieces of information to be specified at the last minute. I would argue that this make the code better (cleaner, less chance of human error in listing lots of parameters, perhaps less data on the call stack, etc.).

Now the question becomes: Did we extract the right pieces of information from the function call into the object? Suppose the code you are writing needs to use a different password for each service you are making a request to, but the content type of the data is always the same. Then it would have made more sense to design our class like this:
class Requestor {
username
contentType

method post(url, data, password) {...}
}
Since we've established that not everyone who is using our code has the same usage patterns, lets design for utimate flexibility. Every parameter can be specified either in the object, or in the function call. Also, if the object has a parameter already, we could override it by passing in that parameter when we call the method. This is not too difficult in in Python, so here is a non-pseudocode example:
class Requestor(object):

def __init__(self, url=None, data=None,
content_type=None, username=None,
password=None):
self.url = url
self.data = data
self.content_type = content_type
self.username = username
self.password = password

def post(self, url=None, data=None,
content_type=None, username=None,
password=None):
url = url or self.url
data = data or self.data
content_type = content_type or self.content_type
username = username or self.username
password = password or self.password
# Now we have our inputs, code to make
# the request starts here
...
If you think this seems a bit excessive, I would agree. I didn't go nearly this far when designing the library that started me thinking about this. There was one request parameter in particular though that does use this pattern. (Five points to the first person to post it in the comments. ;-)

To use the above class, you would do:
requestor = Requestor(username='...')
requestor.password = '...'
...
server_response = requestor.post(url, data, content_type)
It will also handle our alternate usage where we want to give the password to the post method and set the content type at the object level:
requestor = Requestor(username='...', content_type='...')
...
server_response = requestor.post(url, data, password='...')
We can even override parameters which are set in the object:
requestor = Requestor(password='...', content_type='...')
requestor.username = '...'
...
# Override the content_type, just on this request.
server_response = requestor.post(url, data, content_type='...')
With the above example we end up with a lot of code just to let us specify each parameter in either the object or as a function argument. In fact, this can introduce so cases where the user forgets to specify in either, which is possible because all function arguments are now optional. Wouldn't it be better if we could instead specify some of the function parameters, pass the half-specified function call around, and fill in the ramaining values when we finally invoke. For this illustration, I'm using the following syntax to show a partial invocation, < > around arguments instead of ( ).
function post(url, data, contentType, username, password) {...}

started = post<username, password>
...
serverRespense = started(url, data, contentType)
Recall our case from earler, what if the contentType is constant but the password is instead more variable:
started = post<username, contentType>
...
serverRespense = started(url, data, password)
It turns out I'm not the first person to think of this pattern, not by a long shot. Functional programming often makes use of this pattern, referred to as function currying. I found the following example for Scheme which also shows how easy this is in Haskell. The prototype library for JavaScript includes a bind function which can accomplish the same thing. Here's a paper on the topic in C++: (pdf, Google cache HTML). I also found PEP 309 which was a proposal for this in Python. Perhaps I should have called my Python example above: Function Currying using Classes. If you can think of other examples, I'd love to see them.

Sunday, May 17, 2009

Recently completed: our half-US tour

With stops in

Auburn CA
Reno NV
Winnemucca NV
Wells NV
Metropolis NV
Bonneville Salt Flats UT
Salt Lake City UT
Green River WY
Cheyenne WY
Denver CO
Littleton CO
Centennial CO
Lincoln NE
Omaha NE
West Des Moines IA
Indianola IA
Sigourney IA
Fairfield IA
Hannibal MO
Fenton MO
Crestwood MO
Sikeston MO
Memphis TN
Little Rock AR
Texarkana AR
Texarkana TX
Plano TX
Dallas TX
Garland TX
Wichita Falls TX
Amarillo TX
Albuquerque NM
Williams AZ
Bakersfield CA
Plesanton CA
Milpitas CA

(Edited) Here's a map of our approximate route:

View Larger Map

Anticipating "it's all crazy..."

One of my favorite bands is releasing a new album tomorrow and I though I'd take a short moment to gush about it. I've been a long time admirer of mewithoutYou's work. Their abstract poetry filled with vivid imagery combines with highly energetic rich sounds and hits a sweet-spot. Certain lines tend to resonate with me. I find "O Porcupine" in particular to be moving.

I like to think of myself as having broad musical tastes, but if I do tend to be critical of a song it is very often overly simplistic lyrics which I fault. I don't know that I've ever sat down and thought through why that is, perhaps another time.

From listening to a few preview release tracks from this album, I don't think mewithoutYou runs any risk of that sort of problem. In fact, while listening to "the Fox, the Crow and the Cookie" I found myself pulling out the dictionary to look up the words canzonette, corvidae, and escadrille! Some of those rhymes are absolutely majestic. I think this album may satsfy even Xirzec's bombastically erudite lexical tastes. Thanks again to Andy for introducing me to their music.

Tuesday, May 05, 2009

A Two Pun Day

If I were a rock, I wouldn't want to be marble because I wouldn't want people to take me for granite.

I told one of my good friends that I was allergic to milk and he said, "No whey!"

I would estimate that I make at least two puns per week. Something someone says will just strike me at an odd angle and the pun percolates out effortlessly. The other day I thought of the above two. I'm not sure if the first is original, but we were in an elevator debating whether the stone walls were made of marble or granite and I couldn't resist. Later that day, someone mentioned cottage cheese and I thought of the second. It's a certified groaner, five out of six friends agree!

Tuesday, March 17, 2009

Our New Pi Day Laptop

To commemorate this year's pi day, we've christened our newly purchased laptop PiPuter. It's an Acer Aspire. We were looking for a super cheap full sized laptop with decent spects and found a good deal at the MicroCenter. Vanessa says that our computer has graduated magniu cum laude from its factory and it is currently watching us - watching, watching, always watching.

Thursday, March 05, 2009

Septoplasty

I never dreamed that I too would be undergoing septoplasty just a few short months after Joe Greogorio had his operation. It turns out I had a pretty severely deviated septum which likely contributed to the annual sinus infections which I have been experiencing. My allergist recommended I have this operation to straighten the inside of my nose, as my left nasal passage was almost completely blocked near the bridge of my nose. In looking for a possible sinus infection I had a CT scan of my sinuses which revealed just how severe the deviation was. By the way, looking at a slice by slice cross section of your own head is fascinating.

This past Wednesday I had the surgery and I will have the stitches and splints removed next Tuesday. It's been a bit different than I expected. I anticipated some pain but, surprisingly, the part that hurts most is my upper front teeth. I also didn't expect quite so much bleeding. In any case, I'm not feeling too bad and I am looking forward to the long term benefits. Also, this has been my first experience BWOV (blogging while on vicodin).

Sunday, March 01, 2009

Two Guys Arguing - Five Questions

I noticed that my friend Ben is participating in a new blog named Two Guys Arguing (in addition to blogs one two and three :-) and I thought I'd piggy back on a recent ice-breaker post.
  1. What are you currently hacking on?
  2. What are you currently getting better at?
  3. What do you do when your computer is asleep?
  4. Describe that ‘big fish’ project that’s been stewing in your brain.
  5. What are you gonna post about this coming week?

On to the answers:
  1. As a small part of my day job I'm currently working on a rewrite of significant portions of the gdata-python-client to support version two of the Google Data API protocol (check out the v2 branch).
  2. I've been trying to focus on writing speedier unit tests. Unit testing is great, but sometimes it is necessary to write end to end tests which can take a rather long time to run. Slow tests are run less often, so I've been thinking of some ways to cache some of the more expensive pieces of these end to end tests while still preserving their utility.
  3. Playing with Claire is easily one of my favorite non-computer related activities. I have a wonderful wife who keeps me company and there's nothing like relaxing with the family. Aside from the yard work, remodeling, and other house related items, I do very much enjoy playing guitar now and again. I even have a somewhat regular weekly gig.
  4. I have a few side projects I like to hack on here and there in my spare time. One of them I've been thinking more about recently is a computer language I've decided to call "Headspace". I wanted to try an experiment and see what the effects would be of applying the rule of seven to programming.
  5. I've been planning to write a bit about the surgery I've recently undergone so stay tuned ;-)
Two Guys Arguing seems like it's off to a great start and I look forward to reading along.

Wednesday, February 25, 2009

Google Tweets

Earlier today, Google posted the following on Twitter
I'm 01100110 01100101 01100101 01101100 01101001 01101110 01100111 00100000 01101100 01110101 01100011 01101011 01111001 00001010
This looks suspiciously like ASCII, so I set out to render it in a more human readable form:
Bin       Hex ASCII
0110 0110 66 f
0110 0101 65 e
0110 0101 65 e
0110 1100 6C l
0110 1001 69 i
0110 1110 6E n
0110 0111 67 g
0010 0000 20 (space)
0110 1100 6C l
0111 0101 75 u
0110 0011 63 c
0110 1011 6B k
0111 1001 79 y
0000 1010 0A \n
I doubt I'm the first to post this, but there you have it :-)

Sunday, February 01, 2009

Brief Haitus

I'm taking a short break from posting here to study for the GRE. I'm planning to apply to grad school in the not too distant future, and figured I should study. Apologies for the pusillanimously prosaic post.

Tuesday, January 06, 2009

Guitar Hacking

In highschool, I began learning to play guitar. I took private lessons at a local music store, and although I had read sheet music for over seven years by this point (mostly while playing trombone), my teacher started me out by drawing guitar tabs. Unlike sheet music with staffs, clefs, meters, and measures, guitar tabs read like a simple map of where your fingers belong. In some cases, tabs are a picture of the neck of the guitar. For example, most books of guitar chords contain diagrams that look something like this:

E A D G B e
0___________
| | | | | |
| | | | | |
| | | | | |
1-----------
| | | | | |
| 2 | | | |
| | | | | |
2-----------
| | | | | |
1 | | | | 4
| | | | | |
3-----------
| | | | | |
| | | | | |
| | | | | |
4-----------
The long vertical lines are the strings, while the horizontal lines are the frets. The lower pitched strings are on the left, while the higher pitched are on the right. Finger placement is indicated by the numbers written in the strings. The index finger is 1 and the pinky is 4. This diagram is of a G major chord and it uses three fingers, two on the third fret, and one on the second.

However, you could also draw a guitar tab like this:

0 1 2 3 4
e|-------|-------|---4---|-------|
B|-------|-------|-------|-------|
G|-------|-------|-------|-------|
D|-------|-------|-------|-------|
A|-------|---2---|-------|-------|
E|-------|-------|---1---|-------|
The above looks a bit more like the neck of the guitar from the point of view of the person holding it.

One great way to learn how a musician plays an unusual chord, is to look at a video or picture. In that case, you'd see the neck of the guitar reversed, like this:
 
4 3 2 1 0
|-------|---1---|-------|-------|E
|-------|-------|---2---|-------|A
|-------|-------|-------|-------|D
|-------|-------|-------|-------|G
|-------|-------|-------|-------|B
|-------|---4---|-------|-------|e
Unless, of course, you are watching one of the many left-handed guitar players (Hendrix, McCartney, etc.) in which case, you neck would look like this:

0 1 2 3 4
E|-------|-------|---1---|-------|
A|-------|---2---|-------|-------|
D|-------|-------|-------|-------|
G|-------|-------|-------|-------|
B|-------|-------|-------|-------|
e|-------|-------|---4---|-------|
The interesting thing about learning to play guitar using tabs, is that you actually have less information to go on than with sheet music. Tabs tend to lack information about the rhythm being played, tempo, and volume which are all present in sheet music. The one thing which sheet music lacks, however, is an indication of where your fingers should go when playing a particular chord or riff. Unpacking finger placement information from a cluster of notes on a staff can be difficult enough that guitar tabs make an attractive tradeoff. It is the musical equivalent of a domain specific language.

With the prevalence of recorded music which can be rewound and replayed over and over, a guitar player can often reconstruct the rythms and other necessary information by listening to the song. No longer need all information live on the page, the quick and dirty guitar hacker plays with tabs on the stand and the music in her head.