Параллельно с проектом прошел курс «программирование, управляемое данными» и закрепил чтением соответствующей главы из СИКП. Попробую описать, что такое помеченные данные и для чего они нужны.

Пару недель назад я писал о том, как на основе пар создавать составные данные, упаковав ими несколько разных значений в один объект. Вот, например функция для создания точки на плоскости:

const makePoint = (x, y) => cons(x, y);

Имея точки, можно создавать объекты посложнее, например линию или прямоугольник.

const makeLine = (startPoint, endPoint) => 
  cons(startPoint, endPoint);

const makeRect = (topLeft, bottomRight) =>
  cons(topLeft, bottomRight);

Прямоугольник тут расположен параллельно горизонту, поэтому для однозначного его представления достаточно координат двух диагонально противоположных углов. Способ этот не единственный. Такой же прямоугольник можно выразить одной точкой и парой чисел, описывающих его ширину и высоту:

const makeRect2 = (topLeft, width, height) =>
  cons(topLeft, cons(width, height));

Угол прямоугольника при этом не обязательно брать верхний левый, подойдет любой. Если существует много разных способов представить один и тот же объекет, то какой выбрать?

Выбирать один совсем не обязательно. Можно реализовать все способы, а потом использовать их по ситуации. Когда у нас есть два противоположных угла — вызывать makeRect. Когда есть один угол и ширина с высотой — вызывать makeRect2.

a = makeRect(makePoint(0, 2), makePoint(4, 1));
b = makeRect(makePoint(0, 0), 3, 5));

Со множеством вариантов представления одного объекта есть проблема. Допустим у нас есть некий прямоугольник rect, и мы хотим узнать его ширину. В зависимости от того, каким образом представлен этот прямоугольник, к нему нужно применить разные селекторы: rectWidth или rect2Width.

Как определить, каким образом был создан объект? Можно при его создании оставить об этом метку (другим словом — тег).

const makeRect = (topLeft, bottomRight) =>
  tag("twoPoints", cons(topLeft, bottomRight));

const makeRect = (topLeft, width, height) =>
  tag("onePoint", cons(topLeft, cons(width, height)));

Объекты, которые помимо данных содержат тег с типом, и есть помеченные данные.

Теперь, чтобы узнать ширину любого прямоугольника нужно сначала узнать его тип, посмотрев на метку, и, в зависимости от типа прямоугольника, применить нужный селектор:

tag = typeTag(rect);
if (tag === "twoPoints") {
  width = rectWidth(rect);
};
if (tag === "onePoint") {
  width = rect2Width(rect);
};

Способ рабочий, но весьма неудобный. Если в программе для прямоугольника предусмотрено двадцать вариантов представления, то придется проверять объект на соответствие каждому из них. А при добавлении двадцать первого варианта придется искать в коде все эти проверки и добавлять еще одну.

Было бы намного лучше, если один и тот же селектор rectWidth работал вне зависимости от того, какого типа прямоугольник ему дали. О способах реализовать такой селектор в другой раз.