Windows Phone 7: Initial Impressions

I was at PDC in Redmond last week and the cool swag was an LG E900 Windows Phone 7 phone. I dropped in my SIM Card and have been using it as my primary phone for the last couple of days. What follows are my initial impressions.

The Good

  • The hardware is really responsive. This may be because I'm comparing it to a year+ old myTouch running Android 1.6, but the phone seems really, really fast.
  • The Facebook app is pretty awesome. How you feel about this depends on where you fall on the sliding scale of idealism versus cynicism with regards to Facebook.
  • There are actually games available (Yay XNA, I guess).
  • The design of the home screen is really nice.

The Bad

  • When the phone runs low on power, it just shuts off. No warning, one minute you're playing Bejeweled, the next you're staring at a black screen. Trying to turn it on again, results in an LG logo followed by a black screen. I would have thought that the: "Your phone is about to run out of power. You should plug it in now." warnings would be standard by now.
  • The mail client. Other people like it, but I don't. To be fair, that's because GMail has ruined me for all other mail clients forever. The reality is that if it showed me conversations instead of messages and had an archive button instead of a delete button I'd probably love it.
  • The device is a little awkward to hold. The search/back keys occupy the entire bottom bezel and I find myself accidentally pressing them while typing with disastrous results (... as disastrous as interrupting any Facebook interaction can be, that is). The power/lock switch is on the top of the phone making it difficult to lock/unlock the phone one handed.
  • Bing Maps < Google Maps.

The Ugly

  • The browser. It's some hybrid of IE7 and IE, but I gues you can't just jam WebKit on there if you're on Microsoft team.
  • The name: I've got a Windows 7 Phone 7 phone. Come on!1

Verdict

I need to update my myTouch and get something fast and sexy that runs Froyo ;)

More seriously, since checking my email on the subway into work and using Google Maps to find my way around the city are why I have a phone, it's probably not going to be displacing my Android in the immediate future (I'll still play Bejeweled on it though).

[1]Quick, which seven is the one that's not supposed to be there? It's easy to pick on names, but it's always good to remember that nobody could say Wii with a straight face for months, but Nintendo ignored them all while sitting atop the largest pile of money every assembled in all of human history.

New Location, New Tools (redux)

Two years ago I posted that I had moved my blog from Blogger to some django based thing. Recently, I've moved to a different, even more custom, system. In the intervening time, I've written precisely fourteen and one-half blog posts. You may be thinking to yourself: "Surely, that means you spend more time working on your blog's software than actually, y'know, blogging". You would be right, it just turns out that I happen to like it that way.

The new engine is based around the idea of having all of the blog posts stored as reStructuredText documents and then generating all of the indexes/feeds/etc. as a build step producing a bunch of static HTML. This has a couple of neat effects:

  • The entire state of my blog can be stored in version control.
  • I can do all of my editing using my favourite text editor.
  • I can do silly things like running OptiPNG over every single image as a build step.
  • I can say: "My blog software is nginx1."
  • It has the highest YSlow score of anything I have ever worked on.
  • It has a development server (for realz).

Those might sound like the sort of the features that only a programmer could love, well it just so happens that I an one. Sure, this probably only works because (including the post about changing tools) I've only got something like fifteen and two-thirds blog posts, but it all makes more sense if you think of the blog posts I write as cleverly disguised unit tests for whatever blog software I'm working on at the time ;)

[1]It isn't actually. I use Apache just like everybody else. nginx just sounds cooler.

Show Passwords Bookmarklet

I wrote a bookmarklet that makes password inputs show you their contents. The motivation is threefold:

  1. Pasting $('input[type="password"]').val() the console of a page with saved passwords has always amused me and the bookmarklet makes this easier to do on pages that don't have jQuery.
  2. I sometimes actually want to see the password I'm typing, either because it's fiendishly complicated or for some reason I've messed up a bunch and want to verify my typing.
  3. I wanted to play with SASS and CSS linear gradients. So I put together a tiny website and needed something to put up there on it.

Teh Codez

javascript:(function(){
    var passwords = document.querySelectorAll('input[type="password"]');
    for(var i = 0; i < passwords.length; i++){
        passwords[i].setAttribute('type', 'text');
    }
})();
  1. This is a bookmarklet, hence the javascript: protocol. In order for the browser to not navigate to a new page, bookmarklets need to return undefined, hence the anonymous function.
  2. document.querySelector and friends are very cool and they let you find elements jQuery style without needing to try and pull in jQuery to run your three lines of actual code.
  3. Unfortunately, document.querySelectorAll doesn't return a real Array so you can't use Array.forEach and are stuck using JavaScript's built in for. Sad sauce!

More Stack Overflow Stuff

  1. I was on Stack Overflow Podcast #84 along with the rest of the StackExchange team.
  2. I also wrote up some architectural changes to StackExchange.

Krypto and 24

Daily WTF inspired me to implement a Krypto and 24 solver:

import functools
import itertools
import operator

class Node:
        def evaluate(self): raise NotImplemted()
        def print(self): raise NotImplemted()

        def __add__(self, other): return Add(self, other)
        def __sub__(self, other): return Sub(self, other)
        def __mul__(self, other): return Mul(self, other)
        def __truediv__(self, other): return Div(self, other)

        def __str__(self):
                return "<Node: {}>".format(self.print())

        def __repr__(self):
                return str(self)

class Binary(Node):
        def __init__(self, left, right):
                self.left = left
                self.right = right

        def evaluate(self):
                try:
                        return self.op(
                                self.left.evaluate(),
                                self.right.evaluate(),
                        )
                except ZeroDivisionError:
                        return float("NaN")

        def print(self):
                return "({} {} {})".format(
                                self.left.print(),
                                self.symbol(),
                                self.right.print()
                )

        def op(self, left, rigth): raise NotImplemted()
        def symbol(self): raise NotImplemted()

class Unary(Node):
        def __init__(self, op, val):
                self.op = op
                self.val = val

        def evaluate(self):
                return self.op(self.val)

        def print(self):
                return str(self.evaluate())

def BinOp(op, symbol, commutative = True):
        class inner(Binary):
                def op(self, left, right):
                        return op(left, right)

                def symbol(self):
                        return symbol

                @classmethod
                def iscommutative(cls):
                        return commutative

        return inner

Add = BinOp(operator.add, "+")
Sub = BinOp(operator.sub, "-", commutative = False)
Mul = BinOp(operator.mul, "*")
Div = BinOp(operator.truediv, "/", commutative = False)
Lit = functools.partial(Unary, lambda x: x)

OPS = [Add, Sub, Mul, Div]

def generate_expressions(nodes):
        if len(nodes) == 1:
                yield first(nodes)

        for x, y, op in itertools.product(nodes, nodes, OPS):
                isnew = not op.iscommutative() or id(x) > id(y)
                if x is not y and isnew:
                        yield generate_expressions((nodes - set([x, y])) | set([op(x, y)]))

def flatten(xs):
        for x in xs:
                try:
                        for y in flatten(x):
                                yield y
                except TypeError:
                        yield x

def first(xs):
        for x in xs:
                return x

def krypto(nums, solution):
        def issolution(expr):
                return expr.evaluate() == solution

        nodes = set(map(Lit, nums))
        expr = first(filter(issolution, flatten(generate_expressions(nodes))))
        print(expr.print(), "=", expr.evaluate())

if __name__ == "__main__":
        import sys
        args = list(map(int, sys.argv[1:]))
        krypto(args[:-1], args[-1])

My favourite part is that the core of the algorithm is 6 lines:

def generate_expressions(nodes):
        if len(nodes) == 1:
                yield first(nodes)

        for x, y, op in itertools.product(nodes, nodes, OPS):
                isnew = not op.iscommutative() or id(x) > id(y)
                if x is not y and isnew:
                        yield generate_expressions((nodes - set([x, y])) | set([op(x, y)]))

The parametric classes are also a reminder of just how badass python is:

def BinOp(op, symbol, commutative = True):
        class inner(Binary):
                def op(self, left, right):
                        return op(left, right)

                def symbol(self):
                        return symbol

                @classmethod
                def iscommutative(cls):
                        return commutative

        return inner

Edit

2010-05-16: Apparently Python comes with an implementation of cross product.