06 September 2006

Relationships

I have a big file full of thoughts about programming languages. Re-reading it today I found this (edited):

Component-container relationships. I suspect these are all over the place. This is always a hassle. I wish someone would make it easy.

One idea is to put the component class lexically inside the container class:

        class Container {
            class Component {
                // each Component is part of a Container
            }
        }

Hmmm... I need to think about this some more.

And I realized: I now know the solution to this problem. In fact I've implemented it. Relationships.

I mean relationships in the sense of entity-relationship diagrams. Relationships describe how entities relate to each other, like the bolded words below:

His new paper cites 37 sources. (This could perhaps be written in code like this: paper1.cites(paper2))

Tom Hanks starred in the movie Big. (actor.starredIn(movie))

The patient Joe Bloggs is treated in the cardiology ward. (patient.isTreatedIn(ward))

Of course parent-child relationships are one case of this, but there are many others.

Okay. Now consider some hypothetical Python code:

    class Component(object):
        ...

    class Container(Component):
        ...

    # Define the relationship between these two classes...
    containment = OneToManyRel(
        Container, 'container', 'contains', Component, 'contents')

Here's what the OneToManyRel does:

  • Adds a container property to class Component. Each Component's container is always either a Container object or None. It's initially None, but if you like, you can just initialize it in Component.__init__ like any other attribute.

  • Adds the reverse property, contents, to class Container. This property is an object that looks like a set. It's initially empty. The two properties are automatically kept in sync, so if you do thing.container = None, the thing is automatically removed from its container's contents set.

  • Adds a method Container.contains(Component) that can be used to query the relationship.

  • Keeps a data structure of all the relationships, so you can do containment.items(), which returns a list of (Container, Component) pairs.

Here's a taste of how powerful this can be:

    class BuildStep(object):
        ...

    class File(object):
        ...

    # Each BuildStep produces some Files called its targets.
    # Each File is produced by one BuildStep called its buildStep.
    produces = OneToManyRel(BuildStep, 'buildStep', 'produces', File, 'targets')

    # Each BuildStep depends on some Files called its sources.
    # Each File is depended upon by some BuildSteps called its dependants.
    dependsOn = ManyToManyRel(BuildStep, 'dependants', 'dependsOn', File, 'sources')

That's very brief, compared to what you'd have to type to get this the traditional way.

So I have an implementation. In addition to OneToManyRel and ManyToManyRel, it has things called SymmetricRel and EquivalenceRel. (Warts: My current implementation doesn't use weakrefs, so it keeps objects alive until explicitly removed, which is kind of silly. Also, the properties currently look like iterators, not sets.) The whole thing is 355 lines of Python code.

All this is inspired by Inform 7 relations. I wonder about Ruby. I can only assume Ruby on Rails automatically does stuff like this for databases; I don't know about regular old in-memory objects though. I imagine this would be a breeze to implement in Ruby.

Anyway. I don't know how generally useful it will be, but it's certainly encouraging to come across an old gripe about the exact problem this solves.

No comments: