To edit a remote file in emacs using ssh, just do find-file with a filename like this:
/ssh:user@host.example.com:/home/user/fi le.py
Hopefully your ssh agent knows what to do from there.
/ssh:user@host.example.com:/home/user/fi
Hopefully your ssh agent knows what to do from there.
- Mood:
pleased
I was just working on some code, and ran into a puzzling error message in the following code:
sa = (subarchive_t *)malloc(sizeof sa);
if (sa == 0)
return 0;
memset(sa, 0, sizeof *sa);
Can you spot the error? Here's what gcc said:archive.c: In function ‘subarchive_create’: archive.c:347: warning: call to __builtin___memset_chk will always overflow destination bufferLine 347 is the call to memset. When I changed the code to this, the error went away:
sa = (subarchive_t *)malloc(sizeof *sa);
if (sa == 0)
return 0;
memset(sa, 0, sizeof *sa);
This is really cool, because this is the memory allocation mistake I think I make the most, and it's one the compiler should be able to find, but historically hasn't been. I still prefer garbage collection, but I'm really stoked that gcc flagged this error.- Location:DM
- Mood:
pleased - Music:The sound of silence
In case you don't know me, and just wandered by for no obvious reason, a bit of backstory - I'm a coffee nut, have been for quite a while, and through the blessings of good fortune I happen to have a really nice semi-pro espresso machine and grinder at home, so I'm trying to learn how to make really good espresso...
I've been trying to learn technique from the folks who work at Everyman Espresso (my favorite espresso/coffee bar in Manhattan, in the East Village), because they seem to know what they are doing. Last month when I was in New York over Thanksgiving, I was watching one of the baristas work, and noticed that after she foamed the half-and-half (light cream, for folks who don't live in the American Newspeak Zone), she dumped most of it in the sink by pouring it slowly out of the foaming cup. I should stress that she had microfoam, not regular foam; I don't think this would work without microfoam.
( Read more )
I've been trying to learn technique from the folks who work at Everyman Espresso (my favorite espresso/coffee bar in Manhattan, in the East Village), because they seem to know what they are doing. Last month when I was in New York over Thanksgiving, I was watching one of the baristas work, and noticed that after she foamed the half-and-half (light cream, for folks who don't live in the American Newspeak Zone), she dumped most of it in the sink by pouring it slowly out of the foaming cup. I should stress that she had microfoam, not regular foam; I don't think this would work without microfoam.
( Read more )
uma% make ../C/pi ../oc/oc.ot ../oc/oc.ot >oc.c.new && mv oc.c.new oc.c ../C/sym-extract <oc.c >oc.os ../C/sym-gather -d oc.os ../C/pi-api.os >syms.h.new && \ mv syms.h.new syms.h cc -g -O4 -I../C -std=gnu99 -c oc.c ../C/sym-gather -s oc.os ../C/pi-api.os >syms.c.new && \ mv syms.c.new syms.c cc -g -O4 -I../C -std=gnu99 -c -o syms.o syms.c ../C/sym-gather -t oc.os ../C/pi-api.os >symtab.c.new && \ mv symtab.c.new symtab.c cc -g -O4 -I../C -std=gnu99 -c -o symtab.o symtab.c cc -o oc oc.o syms.o symtab.o ../C/libpi-rt.a uma% ./oc ../oc/oc.ot >oc.c.new uma% diff -u oc.c oc.c.new uma%
I should point out that while this is an extremely cool milestone, it's just a milestone. It'll be a while yet before I can actually release anything useful.
I decided to start working on Otter again. I suspect it was playing with Ruby that did it. People always kvetch about the parentheses in Lisp, and then they come up with these incredibly inelegant infix notation procedural programming languages that, while admittedly they do have fewer parentheses, wind up being much harder to read. Ruby has two different block boundary token pairs, for reasons I don't entirely understand. And it's supposed to be lisp-like, but it's just another bodgy syntax to learn, and any lisp-like benefits are pretty much lost on me at this point - it's just PERL, only different. I do like the way Rails is organized, to some extent, but having to program Rails in Ruby kind of takes away from that.
Anyway, Perry and I had a difference of opinion about how to move forward a while back. A long while back, actually. I wanted to get a working compiler ASAP, and to have Otter self-hosting ASAP, so that I could stop writing C code and stop debugging C code. Perry is a sensible person, and therefore wanted to follow a more sensible strategy of getting a fully-featured version of Otter written in C. We also had some differences about how to handle binding. Bottom line, after a rather heartbreaking period of not communicating, we decided to start talking again, but each pursue our preferred strategy.
( Read more )
Anyway, Perry and I had a difference of opinion about how to move forward a while back. A long while back, actually. I wanted to get a working compiler ASAP, and to have Otter self-hosting ASAP, so that I could stop writing C code and stop debugging C code. Perry is a sensible person, and therefore wanted to follow a more sensible strategy of getting a fully-featured version of Otter written in C. We also had some differences about how to handle binding. Bottom line, after a rather heartbreaking period of not communicating, we decided to start talking again, but each pursue our preferred strategy.
( Read more )
- Location:Tucson
- Mood:
optimistic - Music:Widor's 5th (earworm)
We've been having really bad troubles with Speakeasy recently. Basically, they can't get a fast DSL circuit to our house, even though we're quite close to the central office. So I finally threw my hands up in disgust and called Qwest.
The good news is that in fact Qwest delivered a much faster DSL connection. Interestingly, they had to switch me to a port on a less heavily-used concentrator before it worked - before they did that, the symptoms I was experiencing were strangely similar to what I was getting from Speakeasy. I can't help but find this disturbing.
( Read more )
The good news is that in fact Qwest delivered a much faster DSL connection. Interestingly, they had to switch me to a port on a less heavily-used concentrator before it worked - before they did that, the symptoms I was experiencing were strangely similar to what I was getting from Speakeasy. I can't help but find this disturbing.
( Read more )
- Location:Tucson
- Mood:
aggravated
I've been working through a couple of issues with my Vista install since I got it basically working; one is that I need to be able to read Exchange email in Outlook, and the other is that the Python 3.0 program I'm working on failed with a weird error message:
I've also noticed that a lot of problems I run into on Windows aren't documented anywhere on the Internet. Maybe Windows users are used to this, but it's weird for me - when I have a problem with Linux or more particularly Mac OS, generally speaking I can google with the symptoms and come up with a place where someone's blogged a solution, or where it's come up on a mailing list. Not so Windows - usually if an answer turns up, it's on a subscription site, and you can't see the answer without paying.
( Read more )
from pyexpat import *
ImportError: DLL load failed: The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log for more detail.
I've also noticed that a lot of problems I run into on Windows aren't documented anywhere on the Internet. Maybe Windows users are used to this, but it's weird for me - when I have a problem with Linux or more particularly Mac OS, generally speaking I can google with the symptoms and come up with a place where someone's blogged a solution, or where it's come up on a mailing list. Not so Windows - usually if an answer turns up, it's on a subscription site, and you can't see the answer without paying.
( Read more )
- Location:Bowie
- Mood:smug
- Music:The Sound of Silence
I have the bizarre privilege of being the person that provides support to two of my Lamas who have such good karma that they actually find Windows to be as good as Mac OS X. And so it behooves me to occasionally be a Windows user rather than a Mac user, not so much so that I can know enough about Windows that I can help them with it, because they already know more than enough about using Windows, but rather so that I'm motivated to make the code I write work on Windows so that they can use it.
So anyway, here I am running Windows Vista on my MacBook. But there's a problem. The Mac keyboard has the Alt and Win keys in the opposite order that they are on a Windows keyboard. Usually I have to deal with this in reverse when I'm using a Windows keyboard on a Mac, but the Mac has a control panel that lets you swap the modifier keys around on a per-keyboard basis; Windows doesn't. Fortunately, there's a registry edit hack that fixes the problem.
( Read more )
So anyway, here I am running Windows Vista on my MacBook. But there's a problem. The Mac keyboard has the Alt and Win keys in the opposite order that they are on a Windows keyboard. Usually I have to deal with this in reverse when I'm using a Windows keyboard on a Mac, but the Mac has a control panel that lets you swap the modifier keys around on a per-keyboard basis; Windows doesn't. Fortunately, there's a registry edit hack that fixes the problem.
( Read more )
I've been working on getting NetBSD running on the OLPC, because I want to hack on the OLPC, and I just can't cope with the Linux build system. Progress so far is that I've been able to get it to load a NetBSD kernel without segfaulting. If you try to run the kernel, you get an immediate page fault, but that's no surprise since I haven't started hacking locore.S yet.
If you're curious, you can try the kernel config I'm using along with these patches. These apply to NetBSD-current, so if you don't have that you'll need to get it first. Only do this if you're into seriously geekly hacking, since the kernel doesn't even run yet.
If you're curious, you can try the kernel config I'm using along with these patches. These apply to NetBSD-current, so if you don't have that you'll need to get it first. Only do this if you're into seriously geekly hacking, since the kernel doesn't even run yet.
- Location:Up on the mountain
- Mood:
sleepy - Music:The sound of silence
I'm posting this because people are trying to use the IPv6-only network at the IETF, and the Mac doesn't have a way to autoconfigure its DNS server IP address. I've been hacking on a DHCPv6 client for a couple of years now (no, not continuously!), and we did an interoperability test last week at which I was able to test a bunch of features and fix a bunch of bugs, so I'm reasonably happy with where the DHCP client is right now.
Unfortunately, the part I'm happy with is the protocol engine. I haven't quite figured out how to stuff DNS server settings into the SystemConfiguration framework on Mac OS X yet, although I feel like I've gotten fairly close. So I'm offering this DHCP client to folks who want to do DHCPv6 on Mac OS X, and are competent hacking in Python, and are willing to do a little fiddling around to make it work.
I should warn you before you read all the way to the bottom of this post that I can't offer source code for my DHCPv6 client protocol engine right now. I'm not sure when/if that will change; the code doesn't belong to me, so I don't really have any say in it. You do get source to the python driver, though, so you can do some hacking; if the protocol engine is broken, obviously you have to talk to me.
( Read more )
Unfortunately, the part I'm happy with is the protocol engine. I haven't quite figured out how to stuff DNS server settings into the SystemConfiguration framework on Mac OS X yet, although I feel like I've gotten fairly close. So I'm offering this DHCP client to folks who want to do DHCPv6 on Mac OS X, and are competent hacking in Python, and are willing to do a little fiddling around to make it work.
I should warn you before you read all the way to the bottom of this post that I can't offer source code for my DHCPv6 client protocol engine right now. I'm not sure when/if that will change; the code doesn't belong to me, so I don't really have any say in it. You do get source to the python driver, though, so you can do some hacking; if the protocol engine is broken, obviously you have to talk to me.
( Read more )
- Location:Philadelphia IETF
- Mood:wired
- Music:Silence
So the big news today in the world of linux is a security compromise where a rootkit has apparently been installed on a fairly large number of systems and used to distribute viruses to vulnerable Windows machines.
What's interesting about this particular security breach is how it happened: the current theory is that a large number of machines were compromised because they had the same root password. That is, the compromise isn't a problem with linux's security, strictly speaking. Rather, it is a problem with one security culture that is fairly prevalent among unix system admins. I say "unix" because this is really specific to any unix or unix-like system; interestingly, this particular compromise might actually be more difficult to accomplish on a Windows machine because they're so hard to use remotely (unless you're running vnc, but even then, vnc is so clunky that i think it would be hard to compromise a thousand systems quickly).
( Read more )
What's interesting about this particular security breach is how it happened: the current theory is that a large number of machines were compromised because they had the same root password. That is, the compromise isn't a problem with linux's security, strictly speaking. Rather, it is a problem with one security culture that is fairly prevalent among unix system admins. I say "unix" because this is really specific to any unix or unix-like system; interestingly, this particular compromise might actually be more difficult to accomplish on a Windows machine because they're so hard to use remotely (unless you're running vnc, but even then, vnc is so clunky that i think it would be hard to compromise a thousand systems quickly).
( Read more )
- Location:Tucson
- Mood:
optimistic
I had to take apart a Sony VAIO recently to replace a failing hard drive. I happened to have my camera handy, so I took some pictures and took notes. Here are the step by step instructions. Do not follow them. Your VAIO will explode, catch fire, and eat your cat. I include these instructions here merely as a cautionary tale.
First, prepare a place where you're going to put the screws as you remove them. This should be several times larger than the VAIO, so that you can do it in stages. If you don't do this, putting the screws back will be a pain in the neck.
( Read more )
First, prepare a place where you're going to put the screws as you remove them. This should be several times larger than the VAIO, so that you can do it in stages. If you don't do this, putting the screws back will be a pain in the neck.
( Read more )
Since I'm flaming about how wordy educated people are, the pithy summary to my previous article is:
In scheme, "or" needs to be a primitive because:
(1) it has to return the value of its first true parameter, not just #t or #f
(2) to implement it in terms of if requires let
Whereas "and" can be implemented trivially in terms of "if," without using "let."
In scheme, "or" needs to be a primitive because:
(1) it has to return the value of its first true parameter, not just #t or #f
(2) to implement it in terms of if requires let
Whereas "and" can be implemented trivially in terms of "if," without using "let."
- Mood:creative
(BTW, if you are a LISP compiler geek, you already know everything I'm about to say in this article, and you likely have a strong opinion on what conclusion I should have drawn at the end of this article, so you might want to skip ahead to see if you get to flame me or not.)
(A second aside for LISPers: I'm uneducated. I have a glancing knowledge of this stuff from my reading, but I have a very limited grasp on the literature. Sometimes this is a strength: I won't put in an onion just because everybody else put it in, because I probably don't know about it. But sometimes it's a weakness - I really don't know *why* Scheme does
Oh, the other virtue of being uneducated is that it saves time - educated people are incredibly wordy. And a lot of what they say is stuff I don't need to know. It's only worth reading them if the tiny grain of wheat in amongst all that chaff is something I wouldn't have figured out faster than I was able to sort it from the chaff. I know, that's cold, but admit it: you know it's true.
The fact that people still read journal articles is a tribute to just how brilliant some educated people are. But Mother of God, there is *so* much chaff out there these days. So by provoking educated people with my uneducated remarks, I am hoping that they'll reveal their kernels of wheat without making me sort through the chaff. Really, if you think about it, this is the essence of the teacher-student relationship.)
I'm wrestling with a problem with the otter compiler right now, and it boils down to the way we currently implement
That was remarkably unclear, so consider a classic example from C. You want to look at the value of something to which you have a pointer, but only if the pointer is valid. If the pointer isn't valid, then when you try to look at the thing it points to, your program will crash. So you write:
if (x != NULL && *x == true) { ... }
This works, because it never tries to evaluate *x unless x is not null. So && (and) is an operator, but it's a special kind of operator.
So what about in LISP? Well, it turns out that you can sort of implement and and or in terms of if in LISP, because if returns its result. Consider this:
(if a b c)
If a evaluates to truth, then the if statement returns whatever b evaluates to. If a evaluates to false, then the if statement returns whatever c evaluates to. So now you can implement and in terms of if:
(and a b) => (if a b #f)
Or if you want to get fancy and force
(and a b) (if a (if b (#t #f) #f)
So you see, you can implement
(or a b) => (if a #t b)
(or a b) => (if a #t (if b #t #f))
But actually, it turns out that in LISP, or at least in Scheme,
And of course, all values except #f are true. (We can have a big debate about whether '() is true or false, but let's not go there. Personally, I'm nostalgic for nil - I think LISP got that right).
So, how do we implement
(and a b) => (if a b a)
And
(or a b) (if a a b)
Oops, not so fast. We just evaluated a twice. What if it has side effects? What if the value is different the second time you evaluate it (which is a kind of side effect, of course)? Well, we can fix
(and a b) => (if a b #f)
We can do this because we only ever return #f if a evaluated to #f. But any value is truth, so we have to know what a evaluated to in the case of the implementation for
(or a b) => (let ((x a)) (if x x b))
This solves the problem, because we evaluate a only once here. X is a symbol, which evaluates to a value and has no side effects, so we don't have to worry that we used it twice.
But this is a horrible solution, because let actually generates and applies a closure. That's like getting the charcoal in your grill going with liquid oxygen: it works, but you might wish later that you'd just gone out to Wendy's. They can give you fries with your burger, and you're not likely to accidentally burn yourself to a crisp.
So I can see two ways out of this dilemma. The first is to just redefine
The other solution is to make or a primitive, rather than deriving it. I think this is the solution that most Scheme implementations use. So I think that's what I'll do. I guess maybe I won't get flamed, except for the functional programmer comment. But that will be a fun flame war, so go for it.
(A second aside for LISPers: I'm uneducated. I have a glancing knowledge of this stuff from my reading, but I have a very limited grasp on the literature. Sometimes this is a strength: I won't put in an onion just because everybody else put it in, because I probably don't know about it. But sometimes it's a weakness - I really don't know *why* Scheme does
and and or the way it does, and I don't know if there's a good reason why nil mutated into #f and '(), or if it was just done because it seemed more elegant. So when I write stuff like this, I'm *hoping* to get flamed - I'm not writing it to assert that I my understanding is correct.Oh, the other virtue of being uneducated is that it saves time - educated people are incredibly wordy. And a lot of what they say is stuff I don't need to know. It's only worth reading them if the tiny grain of wheat in amongst all that chaff is something I wouldn't have figured out faster than I was able to sort it from the chaff. I know, that's cold, but admit it: you know it's true.
The fact that people still read journal articles is a tribute to just how brilliant some educated people are. But Mother of God, there is *so* much chaff out there these days. So by provoking educated people with my uneducated remarks, I am hoping that they'll reveal their kernels of wheat without making me sort through the chaff. Really, if you think about it, this is the essence of the teacher-student relationship.)
I'm wrestling with a problem with the otter compiler right now, and it boils down to the way we currently implement
and and or. And and or are special in most usable programming languages in that they only evaluate as many arguments as they need to to draw a conclusion. This is handy because it allows you to test one thing, and then not test the next thing if the first thing is true (or false, for or). So the second thing can be something that would throw an error if you evaluated it if the first thing were true (or false, for or).That was remarkably unclear, so consider a classic example from C. You want to look at the value of something to which you have a pointer, but only if the pointer is valid. If the pointer isn't valid, then when you try to look at the thing it points to, your program will crash. So you write:
if (x != NULL && *x == true) { ... }
This works, because it never tries to evaluate *x unless x is not null. So && (and) is an operator, but it's a special kind of operator.
So what about in LISP? Well, it turns out that you can sort of implement and and or in terms of if in LISP, because if returns its result. Consider this:
(if a b c)
If a evaluates to truth, then the if statement returns whatever b evaluates to. If a evaluates to false, then the if statement returns whatever c evaluates to. So now you can implement and in terms of if:
(and a b) => (if a b #f)
Or if you want to get fancy and force
and to return only #t for truth:(and a b) (if a (if b (#t #f) #f)
So you see, you can implement
and really cleanly using a macro. And likewise for or:(or a b) => (if a #t b)
(or a b) => (if a #t (if b #t #f))
But actually, it turns out that in LISP, or at least in Scheme,
and and or aren't supposed to return #t and #f. They are supposed to return the first true value (for or) or the first false value (for and). And in the case of or, if all the values are false, then it returns false. And for and, if all the values are true, it returns the last true value.And of course, all values except #f are true. (We can have a big debate about whether '() is true or false, but let's not go there. Personally, I'm nostalgic for nil - I think LISP got that right).
So, how do we implement
and in terms of if with this restriction?(and a b) => (if a b a)
And
or?(or a b) (if a a b)
Oops, not so fast. We just evaluated a twice. What if it has side effects? What if the value is different the second time you evaluate it (which is a kind of side effect, of course)? Well, we can fix
and pretty easily:(and a b) => (if a b #f)
We can do this because we only ever return #f if a evaluated to #f. But any value is truth, so we have to know what a evaluated to in the case of the implementation for
or:(or a b) => (let ((x a)) (if x x b))
This solves the problem, because we evaluate a only once here. X is a symbol, which evaluates to a value and has no side effects, so we don't have to worry that we used it twice.
But this is a horrible solution, because let actually generates and applies a closure. That's like getting the charcoal in your grill going with liquid oxygen: it works, but you might wish later that you'd just gone out to Wendy's. They can give you fries with your burger, and you're not likely to accidentally burn yourself to a crisp.
So I can see two ways out of this dilemma. The first is to just redefine
and and or. I think this is actually a good solution. Functional programmers will disagree.The other solution is to make or a primitive, rather than deriving it. I think this is the solution that most Scheme implementations use. So I think that's what I'll do. I guess maybe I won't get flamed, except for the functional programmer comment. But that will be a fun flame war, so go for it.
- Location:Tucson
- Mood:arch
- Music:in hiatus
There's a really great article on Ars Technica about why Apple products are so nice. I enjoy it not only because it's a good article, but because of the weird way it connects to my Dharma life.
There's a Dharma practice called Dakshen Nyamje. Dak means "me", or "self". Shen means "other". "Nyam" means equal. "Je" means "to make". So the practice is making oneself equal with others. It's the same practice that the Christians take as their primary mitzvah: "Love thy neighbor as thyself."
Steve Jobs sums it up in a single sentence: We put ourselves in the place of our customers, and we think, "what would I want?"
There's a Dharma practice called Dakshen Nyamje. Dak means "me", or "self". Shen means "other". "Nyam" means equal. "Je" means "to make". So the practice is making oneself equal with others. It's the same practice that the Christians take as their primary mitzvah: "Love thy neighbor as thyself."
Steve Jobs sums it up in a single sentence: We put ourselves in the place of our customers, and we think, "what would I want?"
- Location:tucson
- Mood:
amused - Music:on hiatus
I just stumbled over this Wikipedia article on steam turbines. It's really well-written, and kind of inspiring. You've got to love a phrase like "iso-entropic." Take that, Maxwell! (Not really, but still...)
I wonder if it's possible to generate high-quality steam for a .75kw steam turbine using some kind of solar still arrangement, or whether the variability of the sun makes that impractical for anything but a really large installation.
I wonder if it's possible to generate high-quality steam for a .75kw steam turbine using some kind of solar still arrangement, or whether the variability of the sun makes that impractical for anything but a really large installation.
- Location:Tucson
- Mood:inspired
- Music:Feist
BTW, in case it's not obvious from the appendix, even if you get rid of LET, LET* and LETREC, it's easy to implement them in terms of lambda. So it's not like they're something you can take out of Scheme without taking out lambda. Which would be kind of like throwing the baby, the bathtub and the bathroom out with the bathwater.
People complain about LISP and Scheme because of the parentheses, but really I think the big problem people have with both languages, without really realizing it, is that in addition to the fact that they can do some serious mojo you simply can't do in other languages, a lot of the very straightforward code that's written in LISP and Scheme is still hard to read. I think one of the reasons it's hard to read is that LET and friends are just very weird constructs. This is just a theory, but I'm curious to see if Scheme code written with non-global DEFINE instead of LET is easier to read.
People complain about LISP and Scheme because of the parentheses, but really I think the big problem people have with both languages, without really realizing it, is that in addition to the fact that they can do some serious mojo you simply can't do in other languages, a lot of the very straightforward code that's written in LISP and Scheme is still hard to read. I think one of the reasons it's hard to read is that LET and friends are just very weird constructs. This is just a theory, but I'm curious to see if Scheme code written with non-global DEFINE instead of LET is easier to read.
- Location:Tucson
- Mood:
contemplative - Music:Evanescence: Lacrymosa
I'm feeling a bit blue as a result of some interpersonal problems that, while largely my fault, are still pretty depressing. But some stuff I've been thinking about with respect to Scheme kind of came clear in my mind while I was taking a shower, so I figure I might as well write it down before I go drown my sorrows in a pint of Tofutti. If you're interested in grokking this, but it doesn't make sense to you at first blush because you're not a Schemer or a LISP wizard, please read the Footnote.
Scheme provides four basic ways of creating bindings. The first of these is (DEFINE ...), which creates a global binding. The other three are (LET ...), (LET* ...) and (LETREC ...). Why four different different ways? Why three different versions of LET?
Well, first of all, define creates a global binding. It creates a global binding regardless of the scope in which it's invoked. And define also has special semantics that make it possible to define two functions bound in the global scope that refer to each other:
(define foo (lambda (a) (bar a)))
(define bar (lambda (a) (foo a)))
Note that foo refers to bar, which hasn't yet been defined at the time that foo is defined.
LET does two things - it creates a local scope, and it binds values to local variables in that scope. It binds values to its arguments in an unspecified order, by simply creating an anonymous function with arguments that are the names of the variables to be bound, and then calling the function with the values to bind to it.
Unfortunately, in some cases you need to be able to specify the order in which values are bound. That's what LET* is for. It guarantees the order in which its variables will be evaluated. This lets you do things like (let* ((foo (next-token)) (bar (next-token)) ...) and be sure that foo will contain the token that came before the one in bar.
There's an additional problem, though. What if you're defining functions that need to call each other? Consider the first example again, only let's use LET instead of defining the functions in the global scope:
(let ((foo (lambda (a) (bar a))) (bar (lambda (a) (foo a)))) ...)
This won't work, because bar and foo refer to each other, but their values are computed in the global scope, and there are no bindings for foo and bar in the global scope - we're going to get a compile error, or a runtime error (we'd rather get a compile error, of course).
LETREC solves this problem by creating a scope in which foo and bar are bound to an undefined value. It then computes the values for foo and bar, in that scope binds the values to their respective variables. So at compile time, foo and bar will have values before the bindings for either one are compiled, and this means that they can safely call each other.
So now we have three finely-honed tools for declaring local variables. Each solves a different problem. But here's the thing. Suppose you, the seasoned scheme hacker, write a bunch of code, liberally using LET, LET* and LETREC. And then you hand it to someone that you're trying to sell on the idea of using Scheme as a programming language. If you're lucky, they'll get to the point where they ask you why the hell there are three different ways of defining local variables. More likely, they'll just say "this is too complicated, and I hate all these parentheses," and you'll have another anti-LISP bigot to contend with.
So why not just use LET* instead of LET in every case? LET* places some constraints on the compiler that LET does not; it's possible that this could be turned to some advantage, but I don't know of a case where this becomes crucial. If you know of one, please tell me. For now, I claim that you can safely replace all occurrances of LET with LET*.
What about LET*? Well, I think there's no reason why LETREC shouldn't be ordered, so can't we just use LETREC instead of LET*? There's on argument against it - if your definition of one variable refers to the value of another from the same LETREC, you'll get different behavior with LETREC than you did with LET*. So for backwards compatibility, there's some risk. It's also possible that LETREC might surprise someone. But I think that assumption is incorrect, for reasons I will explain momentarily.
But here's where I think there's an important distinction to be made: LETREC does the extra work largely at compile time, not at runtime. There's no reason why the code produced when compiling LETREC necessarily needs to be bigger than the code produced when compiling LET. The main difference is an issue of in which syntactic scope the expressions are meant to be evaluated. And this is all figured out at compile time. The only case where there's going to be extra code generated is where it's needed.
As for the issue of LETREC surprising someone, here's the thing. As I said, the purpose of LETREC is to establish a lexical scope. This lexical scope turns out to be essentially for the benefit of the compiler, not the runtime. It's okay to implement LETREC so that the following code produces a compile error (indeed, I would argue that it's more correct than allowing the code):
(letrec ((a b) (b 10)))
And I think it's easy to detect during constant folding. So in practice I think it's not a problem to replace all instances of LET* with LETREC. So now we're down to a single LET - should we just call it LET, and give it the semantics of LETREC?
Not so fast. Is LET really the right way to declare variables? In playing with ARC, Paul Graham says that he tried doing things differently, and concluded that this was bad "because it creates unexpected lexical scopes." I haven't figured out what he means by this, and I suspect he's fallen victim to what he would call an onion - an unquestioned assumption that turns out to be based on a practice that is no longer needed. I don't see the harm in creating extra lexical scopes. I could be wrong. But I would claim that we should get rid of LET as well, and also get rid of the idea that DEFINE defines globals. DEFINE just defines variables. If you want to create a new lexical scope, you create it, either intentionally or accidentally, and then you DEFINE bindings in it.
So instead of (let ((a 10) (b 10) ...) you have (do (define a 10) (define b 10) ...)
This does two things. First, it requires you to define globals in a visual context that looks global, rather than allowing you to declare them anywhere. And second, it means that when you decide to declare some more locals, you don't create an extra indent level. And when you hand your code to your friends who program in languages other than Scheme, they won't be needlessly distracted by the four different ways of defining variables.
In order to compile this correctly, your compiler has to pass across each lexical scope twice; once to find all the variables that are defined in that scope, and a second time to compile the code that produces the values that are bound to them. Computer scientists from Switzerland who love single-pass compilers will despise this necessity; I make no apologies. You could probably do it with backpatches instead, but I don't see the point.
Unless this didn't make sense to you, you can skip the footnote.
Scheme has, nominally two different ways to bind symbols (names) to values. Or in C nomenclature, to set the value of variables, although that's only a rough translation. These are (define ...) which creates a global binding and assigns a value to it, and (lambda (...) ...), which creates local bindings; values are assigned to these bindings when the lambda is applied. For instance:
;; Create a binding for the name foo to the value 10 in the global scope:
(define foo 10) ; creates a global binding between foo and the value 10.
;; Create an anonymous function which takes one argument, a, and then apply the
;; function, passing it a value of 20.
((fn (a) (display "a=" a " foo=" foo "\n")) 20)
So within the scope of the anonymous function, a has a value of 20, and foo has a value of 10, meaning that the call to display will print "a=20 foo=10". If you try to refer to a outside of the function, you'll get an error saying that a is unbound; if you refer to foo outside of the function, it'll have a value of 10, because it's global.
Normally when we just want to define a local variable, we don't do the ((fn ...) ...) trick I just showed; rather, there are three macros, LET, LET* and LETREC, each of which has slightly different semantics, all of which declare a global variable and provide a scope in which to use it. For example, the above case would work with any of the three lets:
(let (a 20) (display "a=" a " foo=" foo "\n"))
So by defining a macro, LET, we make it a little easier to define a local variable, but not much. How do LET, LET* and LETREC differ? They differ in two ways: the order in which the arguments are evaluated, and in what environment the arguments are evaluated.
Let's talk a bit about scoping first. Scheme, like C, is lexically scoped. By comparison, python is also lexically scoped; perl and php are dynamically scoped. So is LISP. Let's see if I can describe the difference. First, think about a dynamically scoped language, where you have a function foo, which defines a variable, a, and then calls bar:
(setq a 10)
(defun foo (a) (bar))
(defun mumble (b) (bar))
(defun bar () (display a "\n"))
(foo 20) will print 20; (mumble 20) will print 10. This is because foo defines a in its scope, and then calls bar, so when b looks up a, it finds the a in foo's scope and uses that. When mumble calls bar, it hasn't defined a in its scope, but conveniently, there's a global definition for a, so it uses that. Consider what this looks like in Scheme:
(define a 10)
(define foo (lambda (a) (bar)))
(define mumble (lambda (b) (bar)))
(define bar (lambda () (display a "\n")))
In scheme, (foo 20) will print 10. (mumble 20) will print 10. This is because bar's scope doesn't include its caller's scope, so the fact that foo has defined a local value for a of 20 doesn't affect bar at all - bar doesn't see foo's version of a.
My editorial on this is that it makes scheme code a lot easier to read than lisp code (or, god forbid, perl code). Because when you read bar, you don't have to wonder where the value for a will come from. Because bar doesn't define a, the value for a has to come from the global scope. Debugging someone else's dynamically scoped code is painful, unless they make special efforts not to refer to non-local variables.
So what's the difference between LET, LET* and LETREC? Let's see how they look when you turn them into anonymous lambdas:
(let ((a 10) (b 20)) (+ a b)) -> ((fn (a b) (+ a b)) 10 20)
(let* ((a 10) (b 20)) (+ a b)) -> ((fn (a) ((fn (b) (+ a b)) 20)) 10)
(letrec ((a 10) (b 20)) (+ a b)) -> ((fn (a b) (set! a 10) (set! b 20)) '() '())
At this point I hope you know enough to read the beginning of this posting. If not, tell me what's confusing you, and I'll see if I can fix it. If you think what I've said here is wrong, you're probably correct - I don't claim to be an expert on Scheme or on programming languages in general. These are just my personal observations.
Scheme provides four basic ways of creating bindings. The first of these is (DEFINE ...), which creates a global binding. The other three are (LET ...), (LET* ...) and (LETREC ...). Why four different different ways? Why three different versions of LET?
Well, first of all, define creates a global binding. It creates a global binding regardless of the scope in which it's invoked. And define also has special semantics that make it possible to define two functions bound in the global scope that refer to each other:
(define foo (lambda (a) (bar a)))
(define bar (lambda (a) (foo a)))
Note that foo refers to bar, which hasn't yet been defined at the time that foo is defined.
LET does two things - it creates a local scope, and it binds values to local variables in that scope. It binds values to its arguments in an unspecified order, by simply creating an anonymous function with arguments that are the names of the variables to be bound, and then calling the function with the values to bind to it.
Unfortunately, in some cases you need to be able to specify the order in which values are bound. That's what LET* is for. It guarantees the order in which its variables will be evaluated. This lets you do things like (let* ((foo (next-token)) (bar (next-token)) ...) and be sure that foo will contain the token that came before the one in bar.
There's an additional problem, though. What if you're defining functions that need to call each other? Consider the first example again, only let's use LET instead of defining the functions in the global scope:
(let ((foo (lambda (a) (bar a))) (bar (lambda (a) (foo a)))) ...)
This won't work, because bar and foo refer to each other, but their values are computed in the global scope, and there are no bindings for foo and bar in the global scope - we're going to get a compile error, or a runtime error (we'd rather get a compile error, of course).
LETREC solves this problem by creating a scope in which foo and bar are bound to an undefined value. It then computes the values for foo and bar, in that scope binds the values to their respective variables. So at compile time, foo and bar will have values before the bindings for either one are compiled, and this means that they can safely call each other.
So now we have three finely-honed tools for declaring local variables. Each solves a different problem. But here's the thing. Suppose you, the seasoned scheme hacker, write a bunch of code, liberally using LET, LET* and LETREC. And then you hand it to someone that you're trying to sell on the idea of using Scheme as a programming language. If you're lucky, they'll get to the point where they ask you why the hell there are three different ways of defining local variables. More likely, they'll just say "this is too complicated, and I hate all these parentheses," and you'll have another anti-LISP bigot to contend with.
So why not just use LET* instead of LET in every case? LET* places some constraints on the compiler that LET does not; it's possible that this could be turned to some advantage, but I don't know of a case where this becomes crucial. If you know of one, please tell me. For now, I claim that you can safely replace all occurrances of LET with LET*.
What about LET*? Well, I think there's no reason why LETREC shouldn't be ordered, so can't we just use LETREC instead of LET*? There's on argument against it - if your definition of one variable refers to the value of another from the same LETREC, you'll get different behavior with LETREC than you did with LET*. So for backwards compatibility, there's some risk. It's also possible that LETREC might surprise someone. But I think that assumption is incorrect, for reasons I will explain momentarily.
But here's where I think there's an important distinction to be made: LETREC does the extra work largely at compile time, not at runtime. There's no reason why the code produced when compiling LETREC necessarily needs to be bigger than the code produced when compiling LET. The main difference is an issue of in which syntactic scope the expressions are meant to be evaluated. And this is all figured out at compile time. The only case where there's going to be extra code generated is where it's needed.
As for the issue of LETREC surprising someone, here's the thing. As I said, the purpose of LETREC is to establish a lexical scope. This lexical scope turns out to be essentially for the benefit of the compiler, not the runtime. It's okay to implement LETREC so that the following code produces a compile error (indeed, I would argue that it's more correct than allowing the code):
(letrec ((a b) (b 10)))
And I think it's easy to detect during constant folding. So in practice I think it's not a problem to replace all instances of LET* with LETREC. So now we're down to a single LET - should we just call it LET, and give it the semantics of LETREC?
Not so fast. Is LET really the right way to declare variables? In playing with ARC, Paul Graham says that he tried doing things differently, and concluded that this was bad "because it creates unexpected lexical scopes." I haven't figured out what he means by this, and I suspect he's fallen victim to what he would call an onion - an unquestioned assumption that turns out to be based on a practice that is no longer needed. I don't see the harm in creating extra lexical scopes. I could be wrong. But I would claim that we should get rid of LET as well, and also get rid of the idea that DEFINE defines globals. DEFINE just defines variables. If you want to create a new lexical scope, you create it, either intentionally or accidentally, and then you DEFINE bindings in it.
So instead of (let ((a 10) (b 10) ...) you have (do (define a 10) (define b 10) ...)
This does two things. First, it requires you to define globals in a visual context that looks global, rather than allowing you to declare them anywhere. And second, it means that when you decide to declare some more locals, you don't create an extra indent level. And when you hand your code to your friends who program in languages other than Scheme, they won't be needlessly distracted by the four different ways of defining variables.
In order to compile this correctly, your compiler has to pass across each lexical scope twice; once to find all the variables that are defined in that scope, and a second time to compile the code that produces the values that are bound to them. Computer scientists from Switzerland who love single-pass compilers will despise this necessity; I make no apologies. You could probably do it with backpatches instead, but I don't see the point.
Unless this didn't make sense to you, you can skip the footnote.
Footnote
Scheme has, nominally two different ways to bind symbols (names) to values. Or in C nomenclature, to set the value of variables, although that's only a rough translation. These are (define ...) which creates a global binding and assigns a value to it, and (lambda (...) ...), which creates local bindings; values are assigned to these bindings when the lambda is applied. For instance:
;; Create a binding for the name foo to the value 10 in the global scope:
(define foo 10) ; creates a global binding between foo and the value 10.
;; Create an anonymous function which takes one argument, a, and then apply the
;; function, passing it a value of 20.
((fn (a) (display "a=" a " foo=" foo "\n")) 20)
So within the scope of the anonymous function, a has a value of 20, and foo has a value of 10, meaning that the call to display will print "a=20 foo=10". If you try to refer to a outside of the function, you'll get an error saying that a is unbound; if you refer to foo outside of the function, it'll have a value of 10, because it's global.
Normally when we just want to define a local variable, we don't do the ((fn ...) ...) trick I just showed; rather, there are three macros, LET, LET* and LETREC, each of which has slightly different semantics, all of which declare a global variable and provide a scope in which to use it. For example, the above case would work with any of the three lets:
(let (a 20) (display "a=" a " foo=" foo "\n"))
So by defining a macro, LET, we make it a little easier to define a local variable, but not much. How do LET, LET* and LETREC differ? They differ in two ways: the order in which the arguments are evaluated, and in what environment the arguments are evaluated.
Let's talk a bit about scoping first. Scheme, like C, is lexically scoped. By comparison, python is also lexically scoped; perl and php are dynamically scoped. So is LISP. Let's see if I can describe the difference. First, think about a dynamically scoped language, where you have a function foo, which defines a variable, a, and then calls bar:
(setq a 10)
(defun foo (a) (bar))
(defun mumble (b) (bar))
(defun bar () (display a "\n"))
(foo 20) will print 20; (mumble 20) will print 10. This is because foo defines a in its scope, and then calls bar, so when b looks up a, it finds the a in foo's scope and uses that. When mumble calls bar, it hasn't defined a in its scope, but conveniently, there's a global definition for a, so it uses that. Consider what this looks like in Scheme:
(define a 10)
(define foo (lambda (a) (bar)))
(define mumble (lambda (b) (bar)))
(define bar (lambda () (display a "\n")))
In scheme, (foo 20) will print 10. (mumble 20) will print 10. This is because bar's scope doesn't include its caller's scope, so the fact that foo has defined a local value for a of 20 doesn't affect bar at all - bar doesn't see foo's version of a.
My editorial on this is that it makes scheme code a lot easier to read than lisp code (or, god forbid, perl code). Because when you read bar, you don't have to wonder where the value for a will come from. Because bar doesn't define a, the value for a has to come from the global scope. Debugging someone else's dynamically scoped code is painful, unless they make special efforts not to refer to non-local variables.
So what's the difference between LET, LET* and LETREC? Let's see how they look when you turn them into anonymous lambdas:
(let ((a 10) (b 20)) (+ a b)) -> ((fn (a b) (+ a b)) 10 20)
(let* ((a 10) (b 20)) (+ a b)) -> ((fn (a) ((fn (b) (+ a b)) 20)) 10)
(letrec ((a 10) (b 20)) (+ a b)) -> ((fn (a b) (set! a 10) (set! b 20)) '() '())
At this point I hope you know enough to read the beginning of this posting. If not, tell me what's confusing you, and I'll see if I can fix it. If you think what I've said here is wrong, you're probably correct - I don't claim to be an expert on Scheme or on programming languages in general. These are just my personal observations.
- Location:Tucson
- Mood:
depressed - Music:Marilyn Manson: This Is The New Shit
One of the things I do at Diamond Mountain when teachings are going on is to record them using a video camera. This is a nice idea in theory, but consumes a ton of disk space. So the first thing I want to do after I've recorded them is to compress them.
And it turns out that video compression has really taken a quantum leap forward in the past couple of years - there's an encoding standard called H.264 (why they don't call it Happy Unicorn Encoding, or something memorable like that, I don't know, but there it is). H.264 encoding gets DVD quality in about a third the space. But the bad news is that it's really hard to make H.264-compressed files.
On either my MacBook or the G5, it takes about eight or twelve hours to compress an hour-and-a-half class. So this means that if I shoot six classes over the weekend, I've got almost a week's worth of compression to do when I get home. Worse, iMovie (the free Apple movie app) doesn't let you batch compress, so you can't just queue them all up and walk away. I could buy a copy of Final Cut Pro Studio, but that's quite expensive. Oh, and while you're compressing, your computer is burning watts like they're on sale. This is bad in Tucson in the summer, although in a cold climate in winter it might be okay.
So anyway, along comes Elgato with their hardware H.264 compressor. It's a little USB device, about twice the size of a USB thumb drive, which you plug into your computer, and it accelerates H.264 compression. They say you get about a 4x speedup over using your CPU. And it's a dedicated video processor, so it's not doing everything the hard way; as a consequence, it doesn't generate much heat.
Anyway, I just compressed a video, which I normally expect to take about 10 hours, give or take, and it compressed in about an hour. And my laptop didn't get all hot. So this is really cool - it means that I can compress a ton of video very quickly. I don't think I'm getting quite as good compression as I get when I do two-pass compression with the CPU, but given how much faster it is and how much less power it draws, I'm willing to take the hit.
And it turns out that video compression has really taken a quantum leap forward in the past couple of years - there's an encoding standard called H.264 (why they don't call it Happy Unicorn Encoding, or something memorable like that, I don't know, but there it is). H.264 encoding gets DVD quality in about a third the space. But the bad news is that it's really hard to make H.264-compressed files.
On either my MacBook or the G5, it takes about eight or twelve hours to compress an hour-and-a-half class. So this means that if I shoot six classes over the weekend, I've got almost a week's worth of compression to do when I get home. Worse, iMovie (the free Apple movie app) doesn't let you batch compress, so you can't just queue them all up and walk away. I could buy a copy of Final Cut Pro Studio, but that's quite expensive. Oh, and while you're compressing, your computer is burning watts like they're on sale. This is bad in Tucson in the summer, although in a cold climate in winter it might be okay.
So anyway, along comes Elgato with their hardware H.264 compressor. It's a little USB device, about twice the size of a USB thumb drive, which you plug into your computer, and it accelerates H.264 compression. They say you get about a 4x speedup over using your CPU. And it's a dedicated video processor, so it's not doing everything the hard way; as a consequence, it doesn't generate much heat.
Anyway, I just compressed a video, which I normally expect to take about 10 hours, give or take, and it compressed in about an hour. And my laptop didn't get all hot. So this is really cool - it means that I can compress a ton of video very quickly. I don't think I'm getting quite as good compression as I get when I do two-pass compression with the CPU, but given how much faster it is and how much less power it draws, I'm willing to take the hit.
- Mood:
pleased - Music:Joan Osborne/Pretty Little Stranger/What You Are
So on the topic of extreme geekiness, here's a conundrum. Geeks by and large sit in front of monitors all day. The monitors are usually at a stupid viewing angle, and it's hard to get them into a different angle. We sit in the same seat all the time, so any problems with the seating arrangement are continuous - we don't get to move to a different chair, or anything like that.
And then there's the problem that when we travel, the dimensions of the computer we inevitably bring with us are dictated by the very unfortunate tension between screen size and portability - the bigger the screen, the less portable, and the smaller the screen, the more tempted we are to put a decent monitor in the checked luggage. When Andrea and I drive out to visit her parents, we always bring my big monitor, because there's cargo space. When we fly to visit my folks, we don't bring it, because it's too big to check. As Syndrome would say, "lame lame lame lame lame!"
And that brings us to the next problem. On those fourteen-hour drives to Andrea's folks' house, if I want to work, I have to contort to see the screen in the car. I have a hot laptop sitting on my lap, and I'm scrunched over to look at it, and I probably can't see it because the screen is bathed in partial sunlight, or is reflecting an image of my torso and hands, which are brilliantly lit by the sun. No matter what angle I choose, there's no satisfaction. And Andrea doesn't like to drive at night, which is probably okay because working at night isn't a whole lot better.
Anyway, this brings me to my idea of how I would like to interact with my computer. I don't know why nobody has tried this yet - I think maybe vendors are enjoying the lock-in that selling laptops gives them, and are avoiding going this direction. Here's what a portable computer *should* look like:
Nice bluetooth keyboard. Bluetooth mouse or trackball (why doesn't anybody sell a bluetooth trackball?) Extra credit if you can plug them in on USB and that turns off the radio and charges the battery. That way you can use them on airplanes without breaking any rules. Battery-powered brick with CPU and hard drive, sized to fit comfortably in a glove box. And the thing that makes it all possible: high-resolution video goggles. It would be nice if they could work over high-speed bluetooth, so that you don't have a wire dangling from them, but I'm not sure what the power requirements of this would be - barring better battery technology (say that three times fast!) wires might be a requirement.
Okay, are you done laughing? I know. High-resolution video goggles are beyond geeky, despite all the hot chicks that they show wearing them in the ads. It seems completely ludicrous to propose this, but think about the advantages:
So this is all very exciting, but is it possible? Actually, yes. There are a couple of things to consider. First, you can definitely get high-resolution small displays in quantity. 1920x1200 is no problem. And you can expect one of these displays to draw less power, not more, than a high-resolution LCD screen in your laptop.
Is it practical? Depends. One of the big issues is weight. Right now most of the video goggles I've seen place the weight of the thing on your nose, and stick way out from your face. This is probably the wrong way to do it. It would make more sense to make it into a sort of crown, with the weight resting in a circle from your forehead around to the back of your head.
Another big issue is the optics. Some of the screens that are out there now are reported to induce nausea. Some have variable optics, some don't. I think at a minimum you need variable optics (optics that vary the focus so that the lens in your eye has to adjust), and you also need to be able to adjust the spacing and direction of the screen enough to avoid creating the kind of image mismatch that induces nausea. I don't see any reason to think that these problems are unsolveable, or even difficult to solve.
People are already working on this. Unfortunately, most of the models out there are low resolution - 320x240, consistent with low-resolution iPod video. A few go to 640x480. It seems not completely stupid to try to get into the iPod market rather than the geek market, because there are more people using video iPods than there are geeks, and the plain video iPod doesn't deliver a particularly satisfying experience. But in order for this to really take off, we need real resolution. 1920x1200 is achievable. It needn't be outrageously expensive. I think we'll see it at some point. It's a bit surprising to me that nobody's really tried it yet.
So I started thinking about how to build one. You can get the screen. There's some interface hardware you'd need, but that seems to be pretty easy to get too. How do you deliver the image to the eyeball? You can either have the screen somewhere around back, and run the image around front with light pipes (which you can get retail from Edmund Optics, but they're kind of spendy, and they're round). Or you can suspend the display out in front of the eyes and put the optics out there too. I think this would be kind of thick, but maybe with some of the new lenses that are being developed it's possible.
I think that people aren't going down this path because of the whole wearable computing angle. Wearable computing is a neat idea for a very restricted set of problems, like working in a wearhouse doing inventory, or something like that, but geeky wearable computing is probably a very silly idea in the way it's generally been conceived. The point of this system isn't to be able to walk around town typing. It's to remove the portability constraints that are typical of a really good workstation right now - to bring your workstation where you want to be, or have to be, rather than forcing you to sit in a specific place to work, or not work at all.
And then there's the problem that when we travel, the dimensions of the computer we inevitably bring with us are dictated by the very unfortunate tension between screen size and portability - the bigger the screen, the less portable, and the smaller the screen, the more tempted we are to put a decent monitor in the checked luggage. When Andrea and I drive out to visit her parents, we always bring my big monitor, because there's cargo space. When we fly to visit my folks, we don't bring it, because it's too big to check. As Syndrome would say, "lame lame lame lame lame!"
And that brings us to the next problem. On those fourteen-hour drives to Andrea's folks' house, if I want to work, I have to contort to see the screen in the car. I have a hot laptop sitting on my lap, and I'm scrunched over to look at it, and I probably can't see it because the screen is bathed in partial sunlight, or is reflecting an image of my torso and hands, which are brilliantly lit by the sun. No matter what angle I choose, there's no satisfaction. And Andrea doesn't like to drive at night, which is probably okay because working at night isn't a whole lot better.
Anyway, this brings me to my idea of how I would like to interact with my computer. I don't know why nobody has tried this yet - I think maybe vendors are enjoying the lock-in that selling laptops gives them, and are avoiding going this direction. Here's what a portable computer *should* look like:
Nice bluetooth keyboard. Bluetooth mouse or trackball (why doesn't anybody sell a bluetooth trackball?) Extra credit if you can plug them in on USB and that turns off the radio and charges the battery. That way you can use them on airplanes without breaking any rules. Battery-powered brick with CPU and hard drive, sized to fit comfortably in a glove box. And the thing that makes it all possible: high-resolution video goggles. It would be nice if they could work over high-speed bluetooth, so that you don't have a wire dangling from them, but I'm not sure what the power requirements of this would be - barring better battery technology (say that three times fast!) wires might be a requirement.
Okay, are you done laughing? I know. High-resolution video goggles are beyond geeky, despite all the hot chicks that they show wearing them in the ads. It seems completely ludicrous to propose this, but think about the advantages:
- no glare, so you can sit wherever you want. You know the recent thing about standing workstations where people can exercise while they work? Screw that: sit on your stationary bicycle and generate power for your computer while you're working.
- you can put in optics that vary your viewing distance, so that your eyes don't go bad from focusing the same distance all day.
- Goggles are small - you can keep them in your carry-on baggage along with your power brick, and now you have a 1080p screen to hack on, and you don't have to worry about the person in the seat in front of you leaning back, because there's no laptop on your lap. You can put the brick in the seat pocket, and the keyboard on your lap.
- The biggest weakness in your laptop—the screen—is now a replaceable commodity item. It's not going to get stepped on, it's not the end of the world if you drop it, and when they come out with a newer, faster CPU, you don't have to buy a new screen to go with it. This is a really big deal - screens are expensive.
So this is all very exciting, but is it possible? Actually, yes. There are a couple of things to consider. First, you can definitely get high-resolution small displays in quantity. 1920x1200 is no problem. And you can expect one of these displays to draw less power, not more, than a high-resolution LCD screen in your laptop.
Is it practical? Depends. One of the big issues is weight. Right now most of the video goggles I've seen place the weight of the thing on your nose, and stick way out from your face. This is probably the wrong way to do it. It would make more sense to make it into a sort of crown, with the weight resting in a circle from your forehead around to the back of your head.
Another big issue is the optics. Some of the screens that are out there now are reported to induce nausea. Some have variable optics, some don't. I think at a minimum you need variable optics (optics that vary the focus so that the lens in your eye has to adjust), and you also need to be able to adjust the spacing and direction of the screen enough to avoid creating the kind of image mismatch that induces nausea. I don't see any reason to think that these problems are unsolveable, or even difficult to solve.
People are already working on this. Unfortunately, most of the models out there are low resolution - 320x240, consistent with low-resolution iPod video. A few go to 640x480. It seems not completely stupid to try to get into the iPod market rather than the geek market, because there are more people using video iPods than there are geeks, and the plain video iPod doesn't deliver a particularly satisfying experience. But in order for this to really take off, we need real resolution. 1920x1200 is achievable. It needn't be outrageously expensive. I think we'll see it at some point. It's a bit surprising to me that nobody's really tried it yet.
So I started thinking about how to build one. You can get the screen. There's some interface hardware you'd need, but that seems to be pretty easy to get too. How do you deliver the image to the eyeball? You can either have the screen somewhere around back, and run the image around front with light pipes (which you can get retail from Edmund Optics, but they're kind of spendy, and they're round). Or you can suspend the display out in front of the eyes and put the optics out there too. I think this would be kind of thick, but maybe with some of the new lenses that are being developed it's possible.
I think that people aren't going down this path because of the whole wearable computing angle. Wearable computing is a neat idea for a very restricted set of problems, like working in a wearhouse doing inventory, or something like that, but geeky wearable computing is probably a very silly idea in the way it's generally been conceived. The point of this system isn't to be able to walk around town typing. It's to remove the portability constraints that are typical of a really good workstation right now - to bring your workstation where you want to be, or have to be, rather than forcing you to sit in a specific place to work, or not work at all.
