Как узнать, какой тип значения находится в переменной Perl?
$x
может быть скаляром, ссылкой на массив или ссылкой на хэш (или, возможно, другие вещи).
Как узнать, какой тип значения находится в переменной Perl?
$x
может быть скаляром, ссылкой на массив или ссылкой на хэш (или, возможно, другие вещи).
Perl предоставляет функцию
ref()
, чтобы вы могли проверить тип ссылки до разыменования ссылки...Используя функцию
ref()
, вы можете защитить программный код, из-за которого переменные переменных производят ошибки при использовании неправильного типа ссылки...
$x
всегда скаляр. Подсказка - это сигил $
: любая переменная (или разыменование какого-либо другого типа), начинающаяся с $
является скаляром. (См. Perldoc perldata для получения дополнительной информации о типах данных.)
Ссылка - это просто определенный тип скаляра. Встроенная функция ref
скажет вам, что это за ссылка. С другой стороны, если у вас есть благословенная ссылка, ref
скажет вам только имя пакета, в который была благословлена ссылка, а не фактический тип ядра данных (благословенными ссылками могут быть hashrefs, arrayrefs или другие вещи). Вы можете использовать Scalar :: Util reftype
который скажет вам, что это за тип ссылки:
use Scalar::Util qw(reftype);
my $x = bless {}, 'My::Foo';
my $y = { };
print "type of x: " . ref($x) . "\n";
print "type of y: " . ref($y) . "\n";
print "base type of x: " . reftype($x) . "\n";
print "base type of y: " . reftype($y) . "\n";
... производит вывод:
type of x: My::Foo
type of y: HASH
base type of x: HASH
base type of y: HASH
Для получения дополнительной информации о других типах ссылок (например, coderef, arrayref и т.д.) См. Этот вопрос: Как получить функцию Perl ref() для возврата REF, IO и LVALUE? и perldoc perlref.
Примечание: вы не должны использовать ref
для реализации веток кода с благословенным объектом (например, $ref($a) eq "My::Foo"? say "is a Foo object": say "foo not defined";
) - если вам нужно принимать какие-либо решения в зависимости от типа переменной, используйте isa
(то есть, if ($a->isa("My::Foo") {...
или if ($a->can("foo") {...
). Также см. Полиморфизм.
Скаляр всегда содержит один элемент. Все, что находится в скалярной переменной, всегда является скаляром. Ссылка - это скалярное значение.
Если вы хотите узнать, является ли это ссылкой, вы можете использовать ref
. Если вы хотите знать ссылочный тип, вы можете использовать процедуру reftype
из Scalar :: Util.
Если вы хотите узнать, является ли это объектом, вы можете использовать blessed
подпрограмму из Scalar :: Util. Вы никогда не должны заботиться о том, что такое благословенная посылка. UNIVERSAL
есть несколько методов, чтобы сообщить вам об объекте: если вы хотите проверить, есть ли у него метод, который вы хотите вызвать, используйте can
; если вы хотите увидеть, что он наследует от чего-то, используйте isa
; и если вы хотите увидеть, что объект выполняет роль, используйте DOES
.
Если вы хотите знать, действительно ли этот скаляр действует как скаляр, но привязан к классу, попробуйте tied
. Если вы получили объект, продолжайте свои проверки.
Если вы хотите узнать, выглядит ли это как число, вы можете использовать looks_like_number
из Scalar :: Util. Если это не похоже на число и это не ссылка, это строка. Однако все простые значения могут быть строками.
Если вам нужно сделать что-то более необычное, вы можете использовать такой модуль, как Params :: Validate.
Мне нравится полиморфизм вместо того, чтобы вручную что-то проверять:
use MooseX::Declare;
class Foo {
use MooseX::MultiMethods;
multi method foo (ArrayRef $arg){ say "arg is an array" }
multi method foo (HashRef $arg) { say "arg is a hash" }
multi method foo (Any $arg) { say "arg is something else" }
}
Foo->new->foo([]); # arg is an array
Foo->new->foo(40); # arg is something else
Это намного мощнее, чем ручная проверка, поскольку вы можете повторно использовать свои "проверки", как и любое другое ограничение типа. Это означает, что когда вы хотите обрабатывать массивы, хэши и даже числа меньше 42, вы просто пишете ограничение для "четных чисел меньше 42" и добавляете новый мультиметод для этого случая. "Код вызова" не влияет.
Библиотека вашего типа:
package MyApp::Types;
use MooseX::Types -declare => ['EvenNumberLessThan42'];
use MooseX::Types::Moose qw(Num);
subtype EvenNumberLessThan42, as Num, where { $_ < 42 && $_ % 2 == 0 };
Затем сделайте Foo поддержкой этого (в определении этого класса):
class Foo {
use MyApp::Types qw(EvenNumberLessThan42);
multi method foo (EvenNumberLessThan42 $arg) { say "arg is an even number less than 42" }
}
Затем Foo->new->foo(40)
выводит arg is an even number less than 42
вместо arg is something else
.
обслуживаемой.
В какой-то момент я прочитал достаточно убедительный аргумент Perlmonks о том, что тестирование типа скаляра с помощью ref
или reftype
- плохая идея. Я не помню, кто выдвинул идею вперед или ссылку. К сожалению.
Дело в том, что в Perl существует много механизмов, которые позволяют сделать данный скалярный акт, как будто все что угодно. Если вы tie
обрабатываете файл так, чтобы он действовал как хэш, тестирование с помощью reftype
сообщит вам, что у вас есть файл. Он не скажет вам, что вам нужно использовать его как хэш.
Итак, аргумент пошел, лучше использовать утиную типизацию, чтобы узнать, что такое переменная.
Вместо:
sub foo {
my $var = shift;
my $type = reftype $var;
my $result;
if( $type eq 'HASH' ) {
$result = $var->{foo};
}
elsif( $type eq 'ARRAY' ) {
$result = $var->[3];
}
else {
$result = 'foo';
}
return $result;
}
Вы должны сделать что-то вроде этого:
sub foo {
my $var = shift;
my $type = reftype $var;
my $result;
eval {
$result = $var->{foo};
1; # guarantee a true result if code works.
}
or eval {
$result = $var->[3];
1;
}
or do {
$result = 'foo';
}
return $result;
}
По большей части я на самом деле этого не делаю, но в некоторых случаях у меня есть. Я все еще думаю о том, когда этот подход подходит. Я думал, что брошу эту концепцию для дальнейшего обсуждения. Мне бы хотелось увидеть комментарии.
Обновление
Я понял, что должен высказать свои мысли по этому подходу.
Этот метод имеет то преимущество, что обрабатывает все, что вы на него набрасываете.
У него есть недостаток - быть громоздким и несколько странным. Наткнувшись на это в каком-то коде, я заставил бы меня выдать большой толстый "WTF".
Мне нравится идея проверить, действует ли скаляр как hash-ref, а не хеш-ссылка.
Мне не нравится эта реализация.