Как узнать, какой тип значения находится в переменной 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 и т.д.) см. этот вопрос: Как я могу заставить функцию 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") { ...
). Также см. polymorphism.
reftype($x) eq 'HASH'
может привести к предупреждению. (Ссылка, с другой стороны, удобно возвращает '' для не-ссылок.)
Скаляр всегда содержит один элемент. Все, что находится в скалярной переменной, всегда является скаляром. Ссылка является скалярным значением.
Если вы хотите узнать, является ли эта ссылка, вы можете использовать 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, а не хеш-ссылка.
Мне не нравится эта реализация.