Archive for July 2006
Sunday, July 23, 2006
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:
- A digital watch with one button. Hold the button to advance the time, and release it when done. I think the model would say this requires the optimal 9.5 bits, even better than the analog watch. In the real world, this design would be terrible.
- When you press Set, the digital watch guides you through a binary search of the possible times. At each step, you press Higher, Lower, or Set. From the model’s perspective, this is less efficient than above because you have to choose from three buttons at each step. It also requires more cognitive work on the part of the user, which isn’t measured by the model. This design seems kind of strange, but my intuition is that it would work rather well—if, that is, most watch users cared about setting their watches to arbitrary times.
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
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
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
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
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.”
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))
Cocoa Macros Objective-C Programming
Wednesday, July 12, 2006
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.
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.
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
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("&","&").replace(">",">").replace("<","<")
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(">", ">").replace("<", "<").replace("&", "&")
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
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
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 uses stack inspection to make decorators work with Python 2.3.
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.
Erlang Programming Language Programming
Saturday, July 1, 2006