27.7.06. Scope, Hog Bay Software, and Knowing Your Place

The staggering array of personal shit management (PSM) tools for Mac OS X seems to be growing, constantly. There are Outliners, Notebooks, personal hierarchical databases, and so on.

Many of the tools may look similar on the surface. Besides downloading all of the trials or crawling through the archives of About This Particular Outliner, how can you decide which tool may be right for you? Many companies do nothing but expound on their particular feature set, but when there are many similar features between applications, it’s not always easy to filter out the two or three items that make a particular application different from substantially different from its competitors. Some companies have comparison tables between their product and one or two (usually popular) competitors. But these tables often show the main company’s product as doing more! more! more! for the same or lower price. And while that can be helpful when evaluating your options, it’s not exactly unbiased research that you’re reading.

That’s why I was impressed yesterday when I took a glance at the page for Hog Bay Software’s Mori. The lead-in paragraph and accompanying screen shot didn’t sell me on it. I have too many “digital notebooks” as it is, and the screen shot reminded me of DEVONThink. But I scrolled down the page, skimming the text and looking at screen shots, and at the bottom of the page I found a paragraph with a lead-in Is Mori the best choice for you?

Buying a note manager can be a daunting process with many choices. On one hand you have great outlining programs. If your goal is lists and outlines, try OmniOutliner. On the other hand you have powerful and complex note databases. If your goal is to create reference database of “everything” try DevonThink. But if you need something in-between, give Mori a try. We think it’s the ideal place for your day to day notes, projects, and activities.

Mori Product Information Page, Hog Bay Software, viewed July 26 2006

Wow. I think that’s refreshingly honest. It gives Mori a good scope, and it differentiates itself by saying “if you need more than lists, but less than a database of everything, we’re your app. But if you need those things, by all means, go next door and get them.”

Hog Bay Software now has my attention. Looking at their other offerings I noticed the application Clockwork, a computer timer program. You know, the buzz-me with an alarm, remind of this, etc, kind. Its page also has a “right choice for you?” paragraph. They also are the developers of WriteRoom, a “distraction free full screen writing environment.” WriteRoom is one of those applications like 37Signals’ Writeboard. Simple plain text writing, no formatting toolbars, no wysiwyg (which is still painful in most browsers), just words. 37Signals has a nice post about the difference between word processing and writing. They are different tools with different scopes. For the time in writing when the words matter more than layout and formatting, it’s wonderful to be distraction free.

And it’s refreshing to look at a software page and wonder “but why would I use that if there’s this tool and that tool and that other tool available?” only to find an answer that says “hey, that other tool might work better for you if you’re looking for something that does feature X really well.”

25.7.06. Typography, languages, beauty, ramble ramble ramble

A post that’s been making the rounds lately, it seems, is Tim Bray’s “On Ruby”. Bray praises Ruby as being “remarkably, perhaps irresistibly, attractive,” especially for people (like Bray) who are proficient in Perl and Java. As some see Bray’s post as a pro-dynamic-language post as much as (or more than) a pro-Ruby post, I finally decided to take a read.

As I was reading, this paragraph jumped out at me:

Maybe the single biggest advantage is readability. Once you’ve got over the hump of the block/yield idiom, I find that a chunk of Ruby code shouts its meaning out louder and clearer than any other language. Anything that increases maintainability is a pearl beyond price.

While I’ve still done very little actual coding in Ruby, I have found the above to be true with most code that I’ve come across. I love reading source. Well, perhaps read is not the right term. I love to look at it, just as I love to look at screen shots when reading about any particular piece of desktop software. I have a deep love for design, and much of my own personal artwork is grounded more in design aesthetics than conventional art aesthetics, so I often take a visual approach to code. And what appeals to me at any given moment often shifts.

Ruby code tends to be quite attractive to me these days. I’m not sure why, entirely. But I think Bray hits it on the head in his next paragraph. Funny thing: when I was reading the paragraph quoted above - regarding readability - the thought “hmm, what about Python?” ran through my head. That, too, is answered in the following.

In theory, Python ought to do better, lacking all those silly end statements cluttering up the screen. But in practice, when I look at Python code I find my eyes distracted by a barrage of underscores and double-quote marks. Typography is an important component of human communication, and Ruby’s, on balance, is cleaner. Tim Bray, “On Ruby”, Jul 24 2006

Typography. Could that really be it? You know, he may be right. Phillip Eby recently had a post about Python Based DSLs. He brings up a couple of the elements that seem to give Ruby the power to make nice little domain-specific-languages in, which projects like Rails use to great advantage.

However, Ruby’s advantage in this area basically boils down to two things: being able to apply functions without parentheses, and having code blocks. The first is nice because it makes it possible to create commands or pseudo-statements, and the second is a necessity because it allows those pseudo-statements to encompass code blocks. Without at least the second of these features, Python is never going to be suitable for heavy-duty DSL construction. Phillip J Eby, “Schema Analysis and the need for Python-based DSLs”, Jul 15 2006

I think Eby captures a couple of the more common elements that make this work for Ruby. However, I think there are a couple of other elements that make Ruby look particularly good in this area. The first is Ruby’s use of Symbols. One description I found for Symbols (I forget the source) is that they’re like Strings that you’ll never show to the user. Therefor they don’t need to be printed. They also seem to work with the Ruby’s equivalent (as far as I understand it) of Python’s identity comparison (in python: obj is None, foo is marker). I’d always seen Symbols in Ruby code, but it was Rails that really made me take notice, as they’re used all over the place. Symbols in Ruby are those things that begin with a colon:

finished = Todo.find :all, :include => [:something]
render :action => 'error'

There is something about that that just looks kindof… nice. :all. In my editor, Symbols are colorized differently than strings, which helps them stand out even more. find :all. Not findAll() or find(all=True) or find('all') like one might have in Python (all of which are OK solutions, but man.. those Symbols).

Now as much as the blocks, Symbols, and optional parenthesis seems to help Ruby - it’s the meta-programming that really seems to stand out. This is some ActiveRecord code from Tracks, a GTD-focused task management application written in Rails:

class Project < ActiveRecord::Base

  has_many :todos, :dependent => true
  has_many :notes, :dependent => true, :order => "created_at DESC"
  belongs_to :user
  acts_as_list :scope => :user

  attr_protected :user

  # Project name must not be empty
  # and must be less than 255 bytes
  validates_presence_of :name, :message => "project must have a name"
  validates_length_of :name, :maximum => 255, :message => "project name must be less than %d"
  validates_uniqueness_of :name, :message => "already exists", :scope =>"user_id"

  ...

These are statements in the class level that read… easily. A project has many todos and notes and belongs to a user. It must have a unique name for an individual user that is less than 255 characters. Wow. That is a lot of information in a small space. And you don’t think of has_many as a function or method or something that’s pulling of crazy metaclass trickery: it reads like a statement. I see this done so often in Ruby that writing things like has_many are not that difficult (I’m not talking about the implementation behind it, since O-R mapping is always a beast; I’m talking about how easy it is to operate on the Project class in-line like that).

Now this is doable in Python. Zope 3 has applied similar things, albeit sparingly (and with good cause). You can’t really monkey with the Project class in Python while inside the class statement without resorting (at the least) to execution frame tricks. But one you’re comfortable with them - you can start doing some nice things. In the early days of Zope’s Interface system, declaring support for what was implemented by class instances looked like this:

class Project(Base):
    __implements__ = IProject
    __used_for__ = IUser

Now it can be this:

class Project(Base):
    implements(IProject)
    adapts(IUser)

In a SQLAlchemy based system I’ve worked on for a major project I’m involved with, I’ve applied some of these techniques to yield code like this:

class FeeScale(Base):
    act.baseTable(
        fee_scale, classify='type',
        order_by=fee_scale.c.price_floor,
    )

    @classmethod
    def getFeeForPrice(cls, price, owner, default=None):
        fee = cls.selectFirst(
            (cls.c.price_floor < price)
            & (price <= cls.c.price_ceil)
            & (cls.c.owner_id == owner.id)
        )
        if fee:
            return fee.service_fee
        return default

class RetailerFeeScale(FeeScale):
    act.whenClassified('retailer')
    implements(interfaces.IRetailerFeeScale)
    classProvides(interfaces.IQueryableFeeScale)

    owner = props.One(ILinkedFee['owner'])

class WholesalerFeeScale(FeeScale):
    act.whenClassified('wholesaler')
    implements(interfaces.IWholesalerFeeScale)
    classProvides(interfaces.IQueryableFeeScale)

    owner = props.One(ILinkedFee['owner'])

It doesn’t read quite as nice as the Ruby example above, and it’s from a different problem domain. But it’s not bad… I had to move my validation and relationship-related items into Zope Schema fields which are about specifying design and contracts. Since I use them heavily in order to do things like form generation, ILinkedFee['owner'] contains the information about the relationship, props.One is a Python descriptor that uses that field information in conjunction with SQLAlchemy’s properties to handle relationships between two mapped objects.

class ILinkedFee(Interface):
    owner = schema.HasOne(
        title=u"Owner",
        model=Ref.relative('.base.Owner'),
        options=dict(lazy=True),
    )

There are upsides and downsides to this setup. One really big downside is that there’s no single place to see everything going on with RetailerFeeScale. Earlier versions of this code had functions that played tricks inside of a class suite to spell things like hasOne(...) without assignment or using a second mechanism such as Interface schemas. That code, however, was playing too many tricks and causing too much trouble. And this setup works well with Zope 3.2 and the latticework I’ve put in place to support it.

But it seems like a lot of work, learning metaclasses and descriptors and decorators and getframe() trickery. However, looking at this mammoth project’s code tonight, I’m impressed by how concise and readable most of it is. Ruby and Smalltalk have both been major influences on me lately, even if I seldom get to actually use those languages directly. God I wish for blocks though. I think that the with statement in Python 2.5 will help. A place where Blocks seem especially powerful is their ability to construct an object, yield it to the block, let the block do things with it (set attributes, etc), and then the thing which yielded control can do more things with the object. Hmm. That’s vague. Here’s one case: explicitly saving (flushing) a SQL Alchemy object after doing a bunch of updates. It’s one of those lines that bugs me: it’s something important (it should happen), but it’s not necessarily important to the business logic. It’s small, and it often blends in with its surroundings:

fee = Fee.get(1)
fee.floor = 0.0
fee.ceil = 10.0
fee.price = 1.25
fee.save()

Small annoyance, really. But it does bug me. I believe that the with statement might help out, ensuring that the save happens immediately:

with savable(Fee.get(1)) as fee:
    fee.floor = 0.0
    fee.ceil = 10.0
    fee.price = 1.25

And with that, I put to bed this evening’s incredibly long and pointless rambling, whose beginnings I scarcely remember. Oh yeah. Ruby’s punctuation and typography is beautiful. It really is. And I envy it.

24.7.06. OmniPlan Approaching

I’m not sure what this indicates about me, but I felt such an overwhelming air of excitement and relief that I almost - almost! - had a little tear in my eye when I saw this announcement from the OmniGroup. Omni’s new secret product that’s no longer a secret is (oh boy oh boy oh boy!): OmniPlan. Project management software.

Why am I so excited about project management software? Because there’s very little project management software for the Mac. I know this, because I recently looked for some. We were using an OmniOutliner document to try to capture all of the known things that we needed to do to get this big enormous project out the door, and to come up with some estimations on the time involved. OmniOutliner was OK for this, to some extent: it can have multiple columns, and the columns can be different types including durations. The duration column can be configured to calculate things in working days or 24 hour days, so you can say “that’s going to take 10 hours” and the document can turn that into “1 day 2 hours”. You can also have summaries, where a parent outline element can add (or average or mean) the values of the child nodes. However, with the way that we were entering data, we couldn’t take advantage of this.

As we were fleshing out the development plan, we were unable to easily spell dependencies. We got around this by adding another column, but it was just plain text. One couldn’t look at the list of things to be done and easily decide to do “that big scary thing that needs to be done so we can do these four other things.”

Turning that into a calendar so we could plan time and other commitments appropriately didn’t work out either. We did it manually, on paper, and then turned some of the big milestones into milestone entries in Basecamp. And while Basecamp excels at communication (insofar as everyone involved actually uses it), this level of task / timeline / dependency management is outside of its scope. And I’m glad for it. We seldom need this kind of MS Project style project management… Until this project came along.

So I downloaded a couple of demo versions of different Mac OS X project managers to see how easily I could turn our Plan into something that could better display time estimates, dependencies, and so on.

How easy was it? Not very. I didn’t want deep project management features. I wanted to be able to enter tasks as quickly and easily as I had with OmniOutliner and have the Plan start coming together on its own. But in everything that I tried, there were too many steps, too many dialog boxes, and so on. I don’t mind dealing with inspectors and details later, but I hate when they get in the way of doing simple data entry. And I also hate the tools whose simple data entry is too simple, so that if you enter a lot of items rapidly you then have to spend a lot of time in dialog boxes, tabs, etc, turning it into something real.

I don’t know what OmniPlan is going to look like, or cost, or anything. But Omni’s record of building good solid NeXTStep and Mac OS X applications that are fast, fun, intuitive, and flexible is nearly unbroken. I wish this application had been available a month or two ago, but better now than never. I expect we’ll see a tool that humble programmers such as myself can use without a degree in business or project management.