Известно где работал вот такой код, который по географическим координатам двух точек на Земле определял расстояние между ними:
my $gis = new GIS::Distance;
my $distance = $gis->distance(@coords_pair);
. . .
add_node{$node, 'pair', {
. . .
distance => int($distance + 0.5),
});
В переменной $distance — вычисленное расстояние в километрах, которое округляется и передается функции add_node, чтобы построить XML-узел, вписав расстояние в одноименный атрибут.
Через пару дней мне захотелось показывать, какую часть экватора составляет найденное расстояние. Что может быть проще?
my $fraction = $distance / 40_075.696;
add_node{$node, 'pair', {
. . .
fraction => $fraction,
distance => int($distance + 0.5),
});
Посмотрел на результат, и вижу, что расстояние между Питером и Пекином — 15% длины экватора. Отлично. Но что такое?! — и само расстояние стало 15 километров.
Так и появился бы еще один седой волос, но warn ref $distance легко ответило на вопрос «какого?»:
Class::Measure::Length at /home/ash/. . ./Place/Worker.pm line 493.
Переменная $distance — ссылка на объект типа Class::Measure::Length; такого же типа становится и переменная $reference. Фактически обе переменные указывают на одно и то же. Когда я делю расстояние на длину экватора и сохраняю результат в $reference, я тем самым порчу значение, которое — как я думал — содержится в переменной $distance.
Оказалось, что Class::Measure, от которого унаследован Class::Measure::Length, одним из первых действий переопределяет пять операторов:
use overload
'+'=>\&_ol_add, '-'=>\&_ol_sub,
'*'=>\&_ol_mult, '/'=>\&_ol_div,
'""'=>\&_ol_str;
Как бы я ни пользовался переменной $distance — пытался бы интерполировать его в строке или использовал бы в арифметических выражениях, всегда инициировались переопределенные операторы.
Смотрим на поведение на изолированном примере:
use v5.10;
use strict;
use Class::Measure::Length;
my $m = new Class::Measure::Length(1, 'inch');
my $f = int($m + 0.5);
say $m;
Здесь последовательно вызываются три метода:
_ol_add
_ol_str
_ol_str
Первые два приходятся на строку
my $f = int($m + 0.5);
Сначала вычисляется сумма _ol_add($m, 0.5), а затем результат (типа Class::Measure::Length) стрингифицируется.
Возвращаясь к исходной задаче, видно, что решение-то простое: нужно лишь воспользоваться методом value, чтобы получить расстояние как число:
my $distance = $gis->distance(@coords_pair)->value;
После этого никаких фокусов при делении не проиходит. Всего-то одно сомнительное решение разработчика модуля, и полчаса ненужной отладки в неожиданном месте.
Комментировать