D&D and character: an open letter to Ben Rosenbaum

Hey, Ben—

Dunno if you’ve been following this (guessing not since you’re busy moving) but Wizards of the Coast has backed off significantly from the worldofwarcraftification of 4th Ed. in favor of more improvisation and roleplaying.

There’s a fairly hefty section in the new basic rules (free PDF download) on character personality and background (including sexual preference, gender identity, and gender presentation) that has me thinking about the differences between the D&D approach to building “character” and (my limited experience of) the more ‘actorly’ systems that seem to be in vogue. D&D doesn’t explicitly model your relationships with other players or your current motivation, but it wants to know your name and sex and gender and height and weight and alignment (inescapable :P) and personality quirks and ideals, but it also wants to know what languages you speak and what god you worship or what thieves’ guild you were a member of or what lord’s army you used to serve in. It’s an enormously long list of choices (or dice rolls/table lookups) and while it all provides flavor most of it (even more than in the actorly systems) is going to stay on the mantlepiece during any one play session.

D&D’s default assumption is that every character has a (character-)life-long campaign(-plus) to look forward to and a world to explore / change / transcend, that (despite, certainly, all my experience to the contrary) a character is for life, not just for an evening. Wizards has of course sound economic reasons to try to enmesh you in an ongoing campaign (or at least to entice you with the possibility of one) and to try to interest you in purchasing some or all of their fine collection of geographically- or advanced-character-developmentally-themed sourcebooks, but there is a legit source of reader or rather player pleasure there.

It makes me reevaluate our discovery some years back that my default approach to character in fiction was to situate the character in society and your default approach was to situate the character in a network of interpersonal relationships. Maybe there’s always a never-to-be-written multi-volume epic in the back of my head, whereas you’re focused on completing the story at hand.

—D.

Dependencia and Modernization

So a lot of links were broken when I moved to WordPress, and it’s come to my attention some folks are missing my 1999 “Dependencia and Modernization” paper. Thanks to the magic of the Wayback Machine I’ve been able to retrieve the original PDF and upload it again; you can download here: dependencia.pdf.

Then something only vaguely like a human face filled the screens, its features stretched across asymmetrical expanses of bone like some obscene Mercator projection

(Department of “You were promised an oppressive cyberpunk dystopia”)

Still officially offline, but: while I was off in East Thrace the server that hosted chrononaut.org was hit by something, for the second time in a few weeks, and to save ongoing headaches for myself and for Brandon, who gets the complaints from the hosting company when people complain that chrononaut.org/discontent.com is attacking them with zombie botnets, I decided it was time after fifteen years to get out of the business of being my own webmaster and move this site to WordPress.com.

As you can see it’s still a work in progress—moving all the posts from my most recent WordPress installation (ca. 5 years ago) was pretty quick but the links and images are still mostly broken and there’s still the archives from my two earlier blog incarnations that need to be converted from flat files into something WordPress can import, but I hope to have that done in a week or two. I’m afraid the permalinks I went to so much trouble to preserve when I went off MoveableType are no longer perma-, but at least all the posts should be there.

We now return you to your regularly scheduled no programming.

 

Offline

I’m off tomorrow to Anatolia and East Thrace for two weeks, with only intermittent opportunities to line on to the interweb, and this seems like a good opportunity to take a break from the whole thing, something I haven’t done in a while. (In fact I think the last time I took a holiday from the net we all still had blogs — imagine!) So no blogs (he said, as if there were blogs), no Facebook, no Twitter, no comics, and in general no more clicking on stuff to see if anything new’s happened, not till October if I can make it that far. So I apologize in advance for missing your birthdays and your book tour announcements and so on. I’ll still be checking email and I should be back on chat some time in mid-September.

Won’t someone think of the insensitives?

Every time sexual harassment at conventions comes up, somebody trots out the red herring that it’s just too hard for certain persons to know when they’re being polite and when they’re being rude, and it’s just unfair to ask these poor insensitive people to read the fickle minds of the lovely mysterious creatures they’re attracted to.

To which I say, with as much respect as I can muster: Please fuck off.

Sensitivity can be learned. Manners can be learned. Manners in fandom are not, much as some fans like to pretend otherwise, significantly different from manners outside it. Erring on the side of being a shy violet because you’re afraid of giving offense may cause you to miss out on a sexual opportunity or two over the years, but if it also causes you to miss out on even one occasion of putting some other person (who, we hope, you’re well-disposed toward, right?) in fear and ruining their convention, then it’s a public service and well worth the sacrifice.

There are people in the world who are physically and mentally incapable of learning to tell the difference between courtesy and rudeness but as a proportion their number is vanishingly small, even in fandom, despite what some self-serving fans like to pretend. And even if it weren’t, their right to hit on people does not come close to trumping the right of other people not to be sexually harrassed.

(Please don’t anyone say “What about the Aspies?” which is usually where this goes next. The autistics and the folks with Asperger’s syndrome that I know are too polite to punch you but that won’t stop me wanting to and my blood pressure doesn’t need it this week, for reasons completely unrelated to SFWA.)

(I know this isn’t a new sentiment and I’m sure it’s been put better by other folks, and recently, but it wasn’t being said in certain venues. So I posted there, and I’m reposting here.)

An open letter to the outgoing SFWA board of directors re: Theodore Beale

Dear board members—

I’m sure you’re sick of hearing about Mr. Beale so I’ll be brief: I don’t know if we still (or yet) have a code of conduct but I can’t imagine that if we did have one his use of the @SFWAAuthors Twitter feed to spread his attack on Nora Jemisin would be allowable under it. (The attack itself, even without dragging in @SFWAAuthors, seems to me to clearly violate sections 3, 4, and probably 7 of the one we used to have. Of course, section 10 of that code kind of pulls most of its teeth.)

I’m writing to ask you to take the strongest steps you think are allowable under our current bylaws to discipline Mr. Beale, whether that’s censure, expulsion, or some other punishment to be named later. At this point I’m not sure anything could be better for SFWA’s public image than to have someone like Mr. Beale outside it shouting loudly about how unwelcome he is in it.

And I’d also like to ask you to please pass on to the incoming board my hope that issues of harrassment, professional misconduct, and putting SFWA into disrepute will be high on their priority list for next year.

With sincere thanks for all your hard work, and sincere regret that the bad behavior of a few individuals has overshadowed it recently in the public eye—

—David Moles

More on Builders, or, favor immutability

A while back I posted a basic Builder example on Stack Overflow. User Marcel Stör complained:

Problem with this approach is that you need to define all the members twice, once in the builder and once in the actual class. [This answer] shows how this can be avoided. With that other approach, however, you cannot have final members although the object itself is still immutable.

Marcel Stör

Well, I looked at that answer, from user Yishai, and I didn’t like it.

I don’t know, I’m not a fan of that approach — the builder’s not reusable, it’s not thread-safe, and you can’t design the build product class to guarantee it’s fully initialized at construction. If I was really worried about DRY, I’d probably use an immutable (copy-on-write) builder and make the last builder instance double as the build product’s immutable data.

David Moles

After I wrote that, I thought I should put some code where my mouth was. So: here’s the original version (more or less), with the fields declared in both the Builder and its product, the Widget:

public class Widget implements IWidget
{
    public static class Builder implements IWidgetBuilder<Widget>
    {
        private String name;
        private String model;
        private String upc;
        private double price;
        private Manufacturer manufacturer;

        public Builder ( String name, double price )
        {
            this.name = name;
            this.price = price;
        }

        @Override
        public Widget build ()
        {
            Widget product = new Widget( this );
            validate( product );
            return product;
        }

        @Override
        public Builder manufacturer ( Manufacturer value )
        {
            this.manufacturer = value;
            return this;
        }

        @Override
        public Builder upc ( String value )
        {
            this.upc = value;
            return this;
        }

        @Override
        public Builder model ( String value )
        {
            this.model = value;
            return this;
        }

        private static void validate ( Widget product )
        {
            assert product.getName() != null : "Product must have a name";
            assert product.getPrice() >= 0 : "Product price cannot be negative";
            Manufacturer mfgr = product.getManufacturer();
            if ( mfgr != null )
            {
                String upc = product.getUpc();
                assert upc != null;
                assert upc.startsWith( mfgr.getCode() ) : "Product UPC does not match manufacturer";
            }
        }
    }

    private final String name;
    private final String model;
    private final String upc;
    private final double price;
    private final Manufacturer manufacturer;

    /**
     * Creates an immutable widget instance.
     */
    private Widget ( Builder b )
    {
        this.name = b.name;
        this.price = b.price;
        this.model = b.model;
        this.upc = b.upc;
        this.manufacturer = b.manufacturer;
    }

    public Manufacturer getManufacturer ()
    {
        return manufacturer;
    }

    public String getModel ()
    {
        return model;
    }

    public String getName ()
    {
        return name;
    }

    public double getPrice ()
    {
        return price;
    }

    public String getUpc ()
    {
        return upc;
    }
}

And here’s the new version: the fields are only declared in the Builder, and they’re always final. The Builder itself is immutable, using a copy-on-write pattern, and the last Builder instance becomes the state of the Widget it produces.

public class Widget2 implements IWidget
{
    public static class Builder implements IWidgetBuilder<Widget2>
    {
        private final String name;
        private final String model;
        private final String upc;
        private final double price;
        private final Manufacturer manufacturer;

        public Builder ( String name, double price )
        {
            this( name, null, null, price, null );
        }

        private Builder ( String name, String model, String upc, double price, Manufacturer manufacturer )
        {
            this.manufacturer = manufacturer;
            this.model = model;
            this.name = name;
            this.price = price;
            this.upc = upc;
        }

        public Widget2 build ()
        {
            Widget2 product = new Widget2( this );
            validate( product );
            return product;
        }

        public Builder manufacturer ( Manufacturer value )
        {
            return new Builder( this.name, this.model, this.upc, this.price, value );
        }

        public Builder upc ( String value )
        {
            return new Builder( this.name, this.model, value, this.price, this.manufacturer );
        }

        public Builder model ( String value )
        {
            return new Builder( this.name, value, this.upc, this.price, this.manufacturer );
        }

        private static void validate ( Widget2 product )
        {
            assert product.getName() != null : "Product must have a name";
            assert product.getPrice() >= 0 : "Product price cannot be negative";
            Manufacturer mfgr = product.getManufacturer();
            if ( mfgr != null )
            {
                String upc = product.getUpc();
                assert upc != null;
                assert upc.startsWith( mfgr.getCode() ) : "Product UPC does not match manufacturer";
            }
        }

    }

    private final Builder b;

    /**
     * Creates an immutable widget instance.
     */
    private Widget2 ( Builder b )
    {
        this.b = b;
    }

    @Override
    public Manufacturer getManufacturer ()
    {
        return b.manufacturer;
    }

    @Override
    public String getModel ()
    {
        return b.model;
    }

    @Override
    public String getName ()
    {
        return b.name;
    }

    @Override
    public double getPrice ()
    {
        return b.price;
    }

    @Override
    public String getUpc ()
    {
        return b.upc;
    }
}

Voilà, DRY. Sadly, it only ends up one line shorter (thanks to the extra Builder constructor), but at least it doesn’t define anything twice!

Subclassing with Bloch’s Builder pattern, revised

So last year I posted about using Joshua Bloch’s Builder pattern to create objects from a hierarchy of subclasses.

I realized later that while I did have something like this completely working at one point, I had it working in C#, which has deceptively different generic semantics. (It also has named parameters, which makes the Builder pattern a fair bit less necessary.) As Zdenek Henek demonstrated (several weeks ago, sorry Zdenek!), in Java, the original version I posted doesn’t allow you to call the arguments in any order.

This one does:

class Shape
{
    private final double opacity;

    public double getOpacity ()
    {
        return opacity;
    }

    public static abstract class ShapeBuilder<S extends Shape, B extends ShapeBuilder<S, B>>
    {

        private double opacity;

        @SuppressWarnings( "unchecked" )
        public B opacity ( double opacity )
        {
            this.opacity = opacity;
            return (B) this;
        }

        public abstract S build ();
    }

    private static class DefaultShapeBuilder extends ShapeBuilder<Shape, DefaultShapeBuilder>
    {
        @Override
        public Shape build ()
        {
            return new Shape( this );
        }
    }

    public static ShapeBuilder<?, ?> builder ()
    {
        return new DefaultShapeBuilder();
    }

    protected Shape ( ShapeBuilder<?, ?> builder )
    {
        this.opacity = builder.opacity;
    }
}

class Rectangle extends Shape
{

    private final double height;
    private final double width;

    public double getHeight ()
    {
        return height;
    }

    public double getWidth ()
    {
        return width;
    }

    public static abstract class RectangleBuilder<S extends Rectangle, B extends RectangleBuilder<S, B>> extends ShapeBuilder<S, B>
    {
        private double height;
        private double width;

        @SuppressWarnings( "unchecked" )
        public B height ( double height )
        {
            this.height = height;
            return (B) this;
        }

        @SuppressWarnings( "unchecked" )
        public B width ( double width )
        {
            this.width = width;
            return (B) this;
        }
    }

    public static RectangleBuilder<?, ?> builder ()
    {
        return new DefaultRectangleBuilder();
    }

    protected Rectangle ( RectangleBuilder<?, ?> builder )
    {
        super( builder );
        this.height = builder.height;
        this.width = builder.width;
    }

    private static class DefaultRectangleBuilder extends RectangleBuilder<Rectangle, DefaultRectangleBuilder>
    {
        @Override
        public Rectangle build ()
        {
            return new Rectangle( this );
        }
    }
}

class RotatedRectangle extends Rectangle
{
    private final double theta;

    public double getTheta ()
    {
        return theta;
    }

    public static abstract class RotatedRectangleBuilder<S extends RotatedRectangle, B extends RotatedRectangleBuilder<S, B>> extends Rectangle.RectangleBuilder<S, B>
    {
        private double theta;

        @SuppressWarnings( "Unchecked" )
        public B theta ( double theta )
        {
            this.theta = theta;
            return (B) this;
        }
    }

    public static RotatedRectangleBuilder<?, ?> builder ()
    {
        return new DefaultRotatedRectangleBuilder();
    }

    protected RotatedRectangle ( RotatedRectangleBuilder<?, ?> builder )
    {
        super( builder );
        this.theta = builder.theta;
    }

    private static class DefaultRotatedRectangleBuilder extends RotatedRectangleBuilder<RotatedRectangle, DefaultRotatedRectangleBuilder>
    {
        @Override
        public RotatedRectangle build ()
        {
            return new RotatedRectangle( this );
        }
    }
}

class BuilderTest
{
    public static void main ( String[] args )
    {
        RotatedRectangle rotatedRectangle = RotatedRectangle.builder()
                .theta( Math.PI / 2 )
                .width( 640 )
                .height( 400 )
                .height( 400 )
                .opacity( 0.5d )
                .width( 111 )
                .opacity( 0.5d )
                .width( 222 )
                .height( 400 )
                .width( 640 )
                .width( 640 )
                .build();
        System.out.println( rotatedRectangle.getTheta() );
        System.out.println( rotatedRectangle.getWidth() );
        System.out.println( rotatedRectangle.getHeight() );
        System.out.println( rotatedRectangle.getOpacity() );
    }
}

Note though it requires some unchecked casts, and depends on the convention that each Builder’s generics are self-referential, which could present some risk if it’s extended further. Test early, test often.

Index

Number of books by women appearing in the Locus All-Centuries Poll top 50 best 20th century SF novels: 4.

Number of women authors represented on the same poll: 3.

Number of books by famous male authors appearing twice due to poor data normalization: 1.

Number of books by women appearing on the top 15 best 21st century SF list: 2.

Number of books on the top 15 best 21st century SF list only appearing there on account of the reputation their authors made writing other books back in the 20th century: at least 3.

Number of books sufficiently embarassed to be on the best 20th century SF list that they’ll just hang around the punch bowl for fifteen minutes or so and then quietly slink off: at least 7, if there’s any justice.

Number of books by women appearing in the Locus All-Centuries Poll top 50 best 20th century fantasy novels: 8.

Number of those books not by J.K. Rowling: 5.

Number of books by women appearing in the top 15 best 21st century fantasy novels: 4.

Number of those books not by Lois McMaster Bujold: 2.

Number of books on the best 20th century fantasy list that would arguably deserve to be there even if the rest of the series they’re in had never been written: 6, maybe 7 tops.

Number of books on the best 20th century fantasy list that nobody would remember if it weren’t for the rest of the books in the series: I ran out of fingers.

Number of books on the best 20th century fantasy list that should be embarrassed to be there, but aren’t, that instead are crowded around the bar, drinking up all the good stuff, talking loudly over each other just to hear their own voices and occasionally sneaking uneasy glances at The Master and Margarita and The Once and Future King and The Little Prince and wondering who the hell they are and who let them in: probably 9 or 10.

Number of George R. R. Martin’s current doorstop series that didn’t make any list, probably because the 21st century list only runs to 15 titles: 1