A language feature that I really appreciate in Python is context managers. That’s what the with
keyword does– it starts a new context, setting up code that will be called before and after a block of code. The most common use is with resources, like opening a file. Without it, you might write
f = open('names')
names = f.readlines()
f.close()
However, being required to call functions in a particular order doesn’t make for a nice interface. Plus, what if an exception happens between open
and close
? Using a context manager fixes both of these issues:
with open('names') as f:
names = f.readlines()
The file will be automatically closed when execution leaves the with
block, even if it leaves by way of an exception.
In the project I’m working on right now, I had a pair of functions that always needed to be called in a particular order:
components = componentsOnStory(...)
Record the position of all ‘components’.- Now, the caller performs some action modifying the geometry of the story.
replaceComponents(components, ...)
Put the components back in the correct positions.
This is another example of a less-than-ideal interface where two functions must be called in a particular order. Javascript doesn’t have context managers, but we can approximate them with a function. Here’s what I came up with.
function withPreservedComponents(context, geometry_id, action) {
const components = componentsOnStory(
context.rootState, geometry_id);
action();
replaceComponents(context, components);
}
And here’s what it looks like for the caller:
function moveFaceByOffset(context, payload) {
// ... some code omitted for brevity
const
movedPoints = verticesForFaceId(
face.id, currentStoryGeometry)
.map(v => ({
x: v.x + dx,
y: v.y + dy,
})),
newGeoms = newGeometriesOfOverlappedFaces(
movedPoints,
// Don't consider face we're modifying as a reason to
// disqualify the action.
geometryHelpers.exceptFace(
currentStoryGeometry, face_id),
);
// ... some code omitted for brevity
withPreservedComponents(
context, currentStoryGeometry.id, () => {
newGeoms.forEach(
newGeom =>
context.dispatch('replaceFacePoints', newGeom));
context.dispatch('replaceFacePoints', {
geometry_id: currentStoryGeometry.id,
face_id,
vertices: movedGeom.vertices,
edges: movedGeom.edges,
dx,
dy,
});
});
}
I feel like this is an improvement, because the caller is no longer responsible for remembering to call the replaceComponents
function. The api of the library is smaller – I’m now only exporting the one withPreservedComponents
function.