Archive for July 2006

Sunday, July 23, 2006

Quantitative Design

Humanized has an interesting article about measuring interface efficiency using information theory (via Daniel Jalkut). It’s an idea that deserves some thought, but for the moment I’m unconvinced. The thesis seems reasonable, but the model doesn’t capture the nuances of the real-world examples and therefore it makes predictions that seem intuitively wrong.

The natural language examples make me suspicious right off the bat. The author does draw a distinction between information and meaning, but subsequent paragraphs sometimes conflate these ideas.

Why is efficiency, defined as the amount of information entered, important? There are so many other factors to consider (including time spent waiting, cognitive load, propensity for errors, and ease of error recovery). My first reaction is that it’s better to bundle all the known and unknown factors together and measure efficiency using the time to complete the task.

In the real world, we rarely start from scratch and try to set a specific time. The initial state of the watch and the shape of the delta matter. Usually, we want to adjust the watch a few minutes forward or backward if it’s gained or lost time. Or we want to leave the minutes alone and shift the hours to account for daylight savings time or traveling through a time zone. My digital watch lets me do the daylight savings adjustment using three button presses. Does that make it more than 100% efficient?

Analog watches also make it easy to make small changes to the time, although sometimes the only way to go backwards a few minutes is to go forwards nearly a whole day. Surely these factors matter, but the model doesn’t capture them.

The article asserts that turning the crown of an analog watch represents 9.5 bits because there are 720 possible times. The way this is presented seems like reasoning backwards to me.

What if an analog watch had two knobs to turn, one for each hand. There are 12 positions for the hour hand and 60 for the minute hand. In the real world, this makes it much easier to set the time because you don’t have to go around and around to get to the right hour. But according to the model, the efficiency has gone down because we’re still choosing one of 720 possible times, only now we have to choose between two knobs, too. After all, the digital watch was penalized for having two untouched buttons while in the quasimode of advancing the hour.

Here are two designs that the model predicts would be good:

HashLife

DDJ on how Gosper used a clever representation and memoization of multiple levels of calls to get a huge speedup:

Making a slow program fast can lead to both joy and frustration. Frequently, the best you can do is a low-level trick to double or maybe quadruple the speed of a program; for instance, many readers may have implemented John Conway’s “Game of Life” using bit-level operations for a significant speedup. But sometimes a whole new approach, combining just a few ideas, yields amazing improvements. A simple algorithm called “HashLife,” invented by William Gosper (“Exploiting Regularities in Large Cellular Spaces,” Physica 10D, 1984), combines quadtrees and memoization to yield astronomical speedup to the Game of Life. In this article, I evolve the simplest Life implementation into this algorithm, explain how it works, and run some universes for trillions of generations as they grow to billions of cells.

Wednesday, July 19, 2006

Error Handling Book

Greg Wilson:

I’d really, really like someone to write a book on error handling and recovery: something like 25–30% of the code in large applications is devoted to this, but no one is ever taught how to do it systematically. As distributed systems become the norm, partial failure and fault tolerance are becoming everyday issues; if anyone out there wants to be rich, famous, and popular, there are worse places to start than writing this book.

Tuesday, July 18, 2006

Taxonomy vs Taskonomy

Donald Norman:

In my consulting activities, I often have to explain to companies that they are too logical, too rational. Human behavior seldom follows mathematical logic and reasoning. By the standards of engineers, human behavior can be illogical and irrational. From the standpoint of people, however, their behavior is quite sensible, dictated by the activity being performed, the environment and context, and their higher-level goals. To support real behavior we need activity-centered design.

Sunday, July 16, 2006

Open Sourcing Is Not Easy

Bill Bumgarner:

There is no such thing as “just publish the source and be done with it.” Publishing source—be it under an open or closed license (like the GPL)—changes a product in unavoidably large ways. Sometimes good. Sometimes bad. Always a lot of effort to deal with.

Saturday, July 15, 2006

Not Open Sourcing Apple’s Apps

John Gruber:

Doing your guerrilla UI hacking by way of scripting and plug-ins solves all the forking issues mentioned earlier—you’re not modifying the application itself, on disk, so you won’t interfere with system updates or the interaction between the application and the rest of the system. And it’s easier to share your work with others—“try my script” is a lot more appealing than “try my custom binary of iCal.”

Cocoa foreach Macro

I’ve updated the macro that I use to iterate over Cocoa collections. As before, the idea is that you write foreach( object, collection ), where object is the name of the loop variable and collection is either an NSEnumerator or an object that responds to -objectEnumerator. collection is only evaluated once, and IMP-caching is used to speed up the calls to -nextObject.

Unlike the old version, all the variables are local to the loop; it does not pollute the outer scope. This requires that you set the “C Language Dialect” in Xcode to C99. Also, there is a new macro called foreacht that lets you specify a type for the loop variable. Then you can write foreach( NSString *, string, collection ), and if you write something like [string count] you’ll get a compiler warning. Here’s the code:

#define foreachGetEnumerator(c) \
    ([c respondsToSelector:@selector(objectEnumerator)] ? \
     [c objectEnumerator] : \
     c)
#define foreachGetIMP(e) \
    [foreachEnum methodForSelector:@selector(nextObject)]
#define foreachCallIMP(imp, e) \
    ((IMP)imp)(e, @selector(nextObject))
#define foreachGetFirst(imp, e) \
    (e ? foreachCallIMP(imp, e) : nil)
#define foreach(object, collection) \
    for ( id foreachCollection = collection, \
             foreachEnum = foreachGetEnumerator(foreachCollection), \
             foreachIMP = (id)foreachGetIMP(foreachEnum), \
             object = foreachGetFirst(foreachIMP, foreachEnum); \
          object; \
          object = foreachCallIMP(foreachIMP, foreachEnum) )
#define foreacht(type, object, collection) \
    for ( type foreachCollection = (id)collection, \
               *foreachEnum = (id)foreachGetEnumerator((id)foreachCollection), \
               *foreachIMP = (id)foreachGetIMP((id)foreachEnum), \
               *object = foreachGetFirst(foreachIMP, foreachEnum); \
          object; \
          object = foreachCallIMP(foreachIMP, foreachEnum) )

Update: Matt Neuberg (who prompted this post by sending me his modification of my original foreach to add the type parameter) didn’t like the look of the casts and suggested using nested one-time for loops instead:

#define foreachGetEnumerator(c) \
    ([c respondsToSelector:@selector(objectEnumerator)] ? \
     [c objectEnumerator] : \
     c)
#define foreacht(type, object, collection) \
for ( id foreachCollection = collection; \
      foreachCollection; \
      foreachCollection = nil ) \
    for ( id foreachEnum = foreachGetEnumerator(foreachCollection); \
          foreachEnum; \
          foreachEnum = nil ) \
        for ( IMP foreachNext = [foreachEnum methodForSelector:@selector(nextObject)]; \
              foreachNext; \
              foreachNext = NULL ) \
            for ( type object = foreachNext(foreachEnum, @selector(nextObject)); \
                  object; \
                  object = foreachNext(foreachEnum, @selector(nextObject)) )

And with that foreacht, you can now define foreach like so:

#define foreach(object, collection) foreacht(id, object, (collection))

Wednesday, July 12, 2006

PmWiki

Jan Erik Moström recommends PmWiki, which he says is easy to install and stores its pages in files so that it’s possible to synchronize them or store them in Subversion. The syntax is only somewhat similar to MediaWiki’s, though.

Sudoku Solver

A Sudoku solver in three lines of Perl (via John Gruber). More interesting to me would be a solver that didn’t rely on backtracking.

Apple Store in Portland

Cabel Sasser:

Part of me thinks that, because the Apple Store is modern, it doesn’t fit into their old-timey, victorian vision of the neighborhood. But that’s funny: a street that tries very hard to not be a mall—but really is basically a mall—has become a kind of reverse-mall, where everything still looks the same, but in a different way. You follow me? It’s almost, ironically, a kind of reality distortion field: “if these buildings look like old houses, we can pretend these national chains aren’t here!”

Friday, July 7, 2006

Character-Level Diff in BBEdit

BBEdit has a great Find Differences feature that lets you compare files line-by-line, but sometimes I want to be able to see the differences within the lines. This is especially useful when editing paragraphs of text rather than lines of code. These two scripts let you compare two files at the character level and view the differences in your browser. Unlike with FileMerge, the files can be Unicode and the additions, deletions, and changes are color-coded.

First is a script that uses Python’s difflib to generate an HTML file with the differences and open it in the browser. It requires Python 2.4. Save it in a file called pyopendiff that has execute permissions. The input files can be Unicode, and they are assumed to be in UTF-8 (change the defaultEncoding to macroman if you want) unless there’s a BOM.

Python’s HtmlDiff class wasn’t designed to handle Unicode, so we HTML-escape the strings before diffing them and undo the (now-redundant) HTML-escaping that HtmlDiff would normally do. This approach is buggy in that it will not properly display escaped characters that have been changed (rather than added or deleted). This could be almost fixed by modifying HtmlDiff to use Unicode internally—since Python uses UTF-16 there would likely still be problems with surrogate pairs.

#!/usr/local/bin/python2.4

def main(oldPath, newPath):
    import os, subprocess, tempfile
    differ = MyHTMLDiff()
    output = differ.make_file(htmlStringFromPath(oldPath).splitlines(), 
                              htmlStringFromPath(newPath).splitlines(),
                              htmlNameFromPath(oldPath), 
                              htmlNameFromPath(newPath))
    outPath = os.path.join(tempfile.mkdtemp(), "diff.html")
    writeStringToPath(output, outPath)
    subprocess.call(["/usr/bin/open", outPath])
    
def htmlStringFromPath(path):
    return htmlFromUnicode(unicodeFromPath(path))

def htmlNameFromPath(path):
    import os
    return htmlFromUnicode(unicode(os.path.basename(path), "utf-8"))

def htmlFromUnicode(u):
    escaped = u.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
    return escaped.encode("ascii", "xmlcharrefreplace")

from difflib import HtmlDiff
class MyHTMLDiff(HtmlDiff):
    _styles = HtmlDiff._styles.replace("Courier", "ProFont, Monaco")
    def _format_line(self, *args):
        return unescapeHTML(super(MyHTMLDiff, self)._format_line(*args))

def unescapeHTML(s):
    return s.replace("&gt;", ">").replace("&lt;", "<").replace("&amp;", "&")

def unicodeFromPath(path):
    return unicodeFromString(stringFromPath(path))

def stringFromPath(path):
    file = open(path, "r")
    result = file.read()
    file.close()
    return result

def unicodeFromString(data, defaultEncoding="utf-8"):
    import codecs
    bomToEncoding = {
        codecs.BOM_UTF8: "utf-8",
        codecs.BOM_UTF16_BE: "utf-16-be",
        codecs.BOM_UTF16_LE: "utf-16-le",
    }
    for bom, encoding in bomToEncoding.items():
        if data.startswith(bom):
            data = data[len(bom):]
            break
    else:
        encoding = defaultEncoding
    return unicode(data, encoding)

def writeStringToPath(string, path):
    file = open(path, "w")
    file.write(string)
    file.close()

import sys
main(sys.argv[1], sys.argv[2])

This AppleScript works like the Compare Two Front Documents command (except that the files must be saved to disk). Put it in ~/Library/Application Support/BBEdit/Scripts and assign it a keyboard shortcut.

tell application "BBEdit"
    set p1 to quoted form of POSIX path of ((file of window 1) as alias)
    set p2 to quoted form of POSIX path of ((file of window 2) as alias)
    do shell script "path/to/pyopendiff " & p1 & " " & p2
end tell

Thursday, July 6, 2006

iDVD Doesn’t Phone Home

Mike Evangelist:

The iDVD team (which included me) arrived a little late for the next review meeting, having been delayed by a problem setting up our demo machine. As we came into the room, Steve was saying something like ‘how about instrumentation? wouldn’t that work?’ to those who were already there. I had no idea what he was talking about, but my fog cleared quickly. He was asking Avie and Eddie about the concept of including a ‘metering’ function in iDVD that would count how many time the application was launched and how many discs were burned. This data would be totally anonymous; no IP addresses or user names or anything like that, just the number of times launched and the number of discs burned. It would be great information to have, to help validate the decision to push DVD burning technology.

Wednesday, July 5, 2006

The Design of Everyday Games

Tea Leaves (via Chris Hanson):

The take-away lesson for software developers, I think, is this: in terms of user interaction design, unless you have a damn good reason to do otherwise, design your game as if it will be targeted at a Gameboy Advance. Protect richness, but destroy, annihilate, and eliminate complexity as if you are Genghis Khan. You provide richness in a game by increasing the number of interesting choices the player has to make. Complexity, on the other hand, is created by making it harder to make those choices, or by hiding the interesting choices in a sea of boring ones. Richness is a virtue. Complexity is just retarded.

DecoratorTools 1.1

DecoratorTools uses stack inspection to make decorators work with Python 2.3.

A Taste of Erlang

David Chisnall:

It used to be that if your code ran quite quickly, a year later you could run it twice as fast for the same amount of money. These days, you’re much less likely to get a chip that’s twice as fast, but you may get one with twice as many cores. If your code is highly parallel, you can just spread it out a bit more.…Two features of Erlang, both of which are built into the language, make it especially suited to writing scalable applications: process creation and message passing.

Saturday, July 1, 2006

ATPM 12.07

The July issue of ATPM is out: