Big Mostacho

Dynamic language bashing and other topics by non admin authors.

2009-07-27

Isotropy

Some time ago, a guy from Intel proposed a rectilinear geometry library for Boost. The original announcement is at http://lists.boost.org/Archives/boost/2007/09/127850.php. Renamed boost::polygon, it was recently advanced for review at http://lists.boost.org/Archives/boost/2009/06/153349.php.

Rectilinear geometry deals with axis aligned segments, rectangles, and more general shapes, which Intel needs in the design of microprocessors. One prominent feature from this library is the “isotropic API.” According to Wikipedia, Isotropy is uniformity in all directions. For boost::polygon, this means that no direction or orientation is privileged. Instead of operating on x, y, height, width, top, and bottom, all methods receive necessary directions and orientations as parameters. You say my_rect.get(LEFT) and not my_rect.get_left().When operations to compute the opposite and perpendicular on directions and orientations are added, this indirection enables generic algorithms that work in transposed and symmetric situations. For example, widget arrangement in WPF/Silverlight can be done in half the code, merging the code paths for the vertical and horizontal orientations.

Another arbitrary choice in the specification of rectangles is which subset of top, bottom, height, left, right, and width is given. Horizontally and vertically, any two of the three properties are sufficient to determine the rectangle. Although conversion between representations is easy and constructors can be defined for all combinations of properties, imperative mutation of a rectangle is again arbitrarily defined and may yield representation dependent results. For example, increasing the width is trivial and unequivocal when the rectangle stores left and width. If the rectangle consists instead of left and right coordinates, the updated rectangle could share the same left or the same right coordinate with the original, or maybe it could lie centered over it. One solution is to construct all rectangles from scratch, providing all the arguments. Another is to write and name appropriately the common variations of a rectangle, so that it can be stretched in a given direction or moved a certain way unambiguously.

Related inelegant designs are present in domains distant from geometry. First name and surname can be stored separate or joined, for example. The choice can be hidden behind an encapsulating interface, as was done with rectangle, but the work is seldom worth it when the code is new and often impractical when the code is old. I’d love to have language support for common relations between variables, so that the compiler writes the access and modification interface and the code is uncoupled to the selection of fields actually stored.

2009-06-17

C# giveth, BCL taketh away

The best feature of C# is the tracking of assignment to variables. The compiler marks as error reading from variables that weren't previously assigned to. Unfortunately, the design of the structs of the .Net library, AKA the Base Class Library, hinders the language in this matter.

Even in trivial code, assignment tracking can spot subtle bugs:

string Last(IEnumerable elems)
{
string result;
foreach (var e in elems)
{
result = e;
}
return result; // Use of unassigned local variable 'result'
}

The result variable is assigned only inside the loop. If the sequence is empty the loop doesn't execute and result is never assigned, as the compiler correctly points out.

It's undecidable to exactly determine at compile time if an expression uses unassigned variables. Instead, the C# specification defines at every program point, propagating information from assignment statements through all control flow statement of a function, whether variables are “definitely assigned.” This conservative stance errs on the side of safety, forbidding all unsafe code but also some safe code.

Compilers for languages that don't mandate similar control flow checking sometimes also bark at the use of unassigned variables. The unconstrained implementations check at a certain level of detail, for some subset of the complete language, even depending on unrelated compiler settings. For example, gcc does flow analysis only when optimization is enabled.

On the other hand, the predictable, dependable, and very thorough analysis of C# considers try/catch, goto, and (the sweetest thing) even struct. All fields of struct variables are tracked independently. The assigned part of the struct can be read without problems while the other part and the whole struct itself are still considered unassigned.

The compiler catches that a dimension was missed while mirroring a point:

struct MyPoint
{
public int x;
public int y;
}

MyPoint Opposite(MyPoint p)
{
MyPoint result;
result.x = -p.x;
return result; // Use of unassigned local variable 'result'
}

The error message should mention that the y field is the one unassigned, but it’s terrific as it stands. I already asked for a better error message at connect.microsoft.com.

The integration with struct is useful for lightweight named parameter emulation and other uses where a named bundle of variables make sense. Alas, the structs predefined in the .Net library put this niceties out of reach. Take System.Windows.Point:

public struct Point
{
double _x;
double _y;
public double X
{
get
{
return this._x;
}
set
{
this._x = value;
}
}
public double Y
{
get
{
return this._y;
}
set
{
this._y = value;
}
}
}

The fields are private and must be accessed through the properties. The adaptation of the Opposite() function seen before encounters compile errors whose fixing also masks the error we wanted the compiler to give us:

Point Opposite(Point p)
{
Point result;
result.X = -p.X; // Use of unassigned local variable 'result'
return result;
}

Because result.X is a property and not a field, setting it amounts to calling an instance method. Of course, instance methods require all arguments, including the implicit this, to be assigned, so result.X can't be written to unless result is initialized when declared:

  Point result = new Point()

But the constructor also initializes result._y so our original error message is also silenced :(

Contributors