OpenRefine and the API/GUI isomorphism

06 May 2018

OpenRefine is a piece of software, originally "Freebase Gridworks", and then renamed to "Google Refine" when Google acquired Metaweb Technologies, and then renamed to "OpenRefine" when Google stopped active support for it.

OpenRefine is for working with, particularly cleaning, messy datasets.

Unrelatedly, I have been thinking about an idea that I think of as "the API/GUI isomorphism". It's a kindof pompous or mathematical name for a pretty simple idea - that there are some moderately natural correspondences between a graphical user interface and a programmatic interface. For example, if there is a button on some panel labeled "foo", maybe might be a zero-argument method (on some object) named "foo()". If there is a pair of a text field and a button, such that you generally need to fill in the text field and then press the button, maybe that corresponds to a one-argument method. If pressing the button causes a new panel to be created, then maybe the method corresponding to the button would return a new object.

By translating an instance (either a GUI or an API) that you are trying to learn into the other modality, you can get some advantages. On the one side, you can learn the transformed thing better because you have thought more deeply about it; translating across the GUI/API isomorphism is a technique for learning. On the other side, you might find something useful - you might invent a nice API via a habit of translating the GUIs that you look at, or you might figure out some principle of how GUIs are constructed by a habit of translating APIs to them.

The very first step of working with data in OpenRefine looks like this:

Screenshot of OpenRefine

The top-right button, "Create Project", causes the GUI to leave this mode, and go on to another mode. What might that look like in code?

Project students_by_institution = openrefine.CreateProject(PARSE_AS_EXCEL, ["Table_1"], {
  ignore_first: 1, // lines at the beginning of the file
  parse_next: 5, // lines as column headers
  discard_initial: 1, // rows of data
  store_blank_rows: true, 
  store_blank_cells_as_nulls: true
});

I think if you study the UI of an open-source tool like OpenRefine, and wrote these kind of speculative "I think the API for this would probably look like this", you could bring those ideas profitably to the source code. Either you find the source code IS organized as you expected, in which case you have shortcutted learning it, or you find the source code is not organized the way you expected and it should be organized the way you described, in which case you have shortcutted designing an improved API. Or, thirdly and most interestingly, you find that the source code is not organized the way you expected and it is better, which is treasure - exactly kind of gap between two parallel designs that you can probably turn into an insight about how to design software.

The cairo 2d graphics library has a celebrated interface. It is frequently bound to new languages and ported to new platforms. I have picked out some of (but not most of) the C++ port of the cairo interface.

class path_builder {
  public:
  [...]
  void line_to(const point& pt);
  void move_to(const point& pt);
  void close_path();
  [...]
};

class surface {
  public:
  [...]
  void set_path(const path& p);
  void fill();
  void stroke();
  void write_to_png(const ::std::string& filename);
  [...]
};

Imagine a bare-bones vector graphics editor, with a large central pane showing a vector image. It has two modes. In one mode, you are creating one particular path; there are "marching ants" showing you what the current path is. You might left-click on a point to extend the path, that is, "line_to" that point. You might right-click on a point to start a new disjoint sub-path, that is, "move_to" that point. There is a button to close the current sub-path off, that is, extend the path with a "line_to" the most recent point that you moved to.

In another mode, you can select one of several previously-created paths, and press a "fill" button - to fill the path with the current color - or you can press a "stroke" button, to draw a line of the current color along the path. Finally, there is a "write_to_png" button (and adjacent text field) that lets you save your image to a file.

Of course, this is only a tiny piece of the Cairo interface, but I believe as you look at more of it, and consider what it would look like on the other side of the API/GUI isomorphism, it starts looking more like a vector graphics editor, not less.