Абстракция
Пары отлично подходят для представления рациональных чисел. Первый элемент пары может выступать числителем, а второй элемент — знаменателем.
a / b → (a, b)
Мы увидели, как пары могут использоваться для представления разных структур, а сочетания таких представлений — создавать новые структуры.
Рациональные числа
Ещё одной очень простой и интересной абстракцией, помимо графических примитивов, являются так называемые рациональные числа. С рациональными числами знакомы все. Рациональным называют число, которое можно представить в виде обыкновенной дроби. Обыкновенная дробь состоит из двух чисел — числителя и знаменателя, поэтому эта абстракция идеально ложится на пары. Интерфейс нам уже знаком и состоит из конструктора и двух селекторов:
import static Rational.denom;
import static Rational.make;
import static Rational.numer;
var rat = make(5, 4);
numer(rat); // 5
denom(rat); // 4
Конструктор make
принимает два числа. Селектор numer
ответственен за получение числителя, denum
— знаменателя.
На рациональных числах определены различные операции, например, сложение рациональных чисел, умножение — всё это происходит по определённым правилам:
n1/d1 + n2/d2 = (n1 * d2 + n2 * d1) / (d1* d2)
n1/d1 * n2/d2 = (n1 * n2) / (d1 * d2)
А по такой формуле работает функция isEqual
, которая проверяет равенство дробей:
n1/d1 = n2/d2, *if* n1 * d2 = n2 * d1
Абстракция позволяет нам делать некоторые интересные вещи так, чтобы остальная часть программы об этом не знала. Например, нормализация дроби:
add(make(1, 10), make(4, 10)); // 5/10
Сложив два числа из примера, мы получим 5/10
, что является одним из представлений числа 1/2
или 10/20
(обычно нормализуют в меньшую сторону). Когда мы говорим про работу с абстракцией, у нас есть несколько способов провести нормализацию числа. Например, это можно делать при вызове конструктора:
make(5, 10); // 1/2
Тогда нормализация будет производиться один раз при создании рационального числа. Другой способ — делать нормализацию при вызове селекторов:
numer(make(5, 10)); // 1
denom(make(5, 10)); // 2
При этом снаружи будет совершенно всё равно, какой способ мы используем. Очевидно, что в данном конкретном случае использование первого способа предпочтительней с точки зрения производительности, потому что селекторы будут вызываться намного чаще конструктора, и производить вычисления каждый раз (а они могут быть довольно объёмными) просто неэффективно. Важно понимать на концептуальном уровне, что способ не один, и его выбор зависит от требований к конкретной системе. Поскольку мы построили барьер абстракции, и манипуляции с рациональными числами производятся только через конструктор и селекторы, то оптимизацию можно спокойно отложить на более поздний срок.
Есть ещё один интересный случай — нормализация знака. Например, при создании рационального числа, в котором числитель или знаменатель отрицателен, нам нужно как-то сохранить информацию об этом, чтобы само рациональное число было отрицательным:
make(-1, 2); // - 1/2
Мы можем договориться, что знак хранится в числителе, и потом этот факт везде использовать. Если же отрицательное значение имеет знаменатель, то мы производим нормализацию, перемещая знак в числитель для единообразной работы. Наружу выставляется удобный интерфейс, чтобы пользователю не приходилось задумываться о правильном использовании знака. Всю работу по приведению к нормальной форме мы делаем внутри функции.
При этом есть различные пограничные случаи, например:
add(make(-1, -4), make(2, 4)); // 3/4
В примере выше и числитель и знаменатель в первом рациональном числе — отрицательные числа. В таком случае два знака -
у дроби должны уходить. А значит, функция make
должна знать об этом. Само по себе это не заработает, и вместо правильного результата могут появляться совершенно непредсказуемые эффекты.
Абстракция данных позволяет нам откладывать момент нормализации до той поры, когда нам действительно понадобится такой функционал. Причём мы можем реализовать его необходимым нам способом. Прикладной код от этого не поменяется — для него реализация значения не имеет.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.