Параллельно с проектом прошел курс «программирование, управляемое данными» и закрепил чтением соответствующей главы из СИКП. Попробую описать, что такое помеченные данные и для чего они нужны.
Пару недель назад я писал о том, как на основе пар создавать составные данные, упаковав ими несколько разных значений в один объект. Вот, например функция для создания точки на плоскости:
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 работал вне зависимости от того, какого типа прямоугольник ему дали. О способах реализовать такой селектор в другой раз.