Как я могу сказать, какой тип значения в переменной Perl?

45

Как узнать, какой тип значения находится в переменной Perl?

$x может быть скаляром, ссылкой на массив или ссылкой на хэш (или, возможно, другие вещи).

Теги:

5 ответов

45
Лучший ответ

ref():

Perl предоставляет функцию ref(), чтобы вы могли проверить тип ссылки до разыменования ссылки...

Используя функцию ref(), вы можете защитить программный код, из-за которого переменные переменных производят ошибки при использовании неправильного типа ссылки...

  • 3
    Я думал, что ref () только скажет вам, что это за ссылка, и ничего не выдаст, если она не одна.
  • 8
    @Chris: Правильно, поэтому, если переменная не является ссылкой, вы можете сделать вывод, что это простой скаляр из того факта, что она ничего не возвращает. В противном случае вы будете знать, что это за ссылка.
Показать ещё 5 комментариев
38

$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.

  • 3
    Обратите внимание, что reftype по определению нарушает инкапсуляцию, поэтому его следует избегать, если у вас нет веских причин.
  • 2
    Если вы используете reftype, имейте в виду, что он возвращает undef для не-ссылки, поэтому код вроде reftype($x) eq 'HASH' может привести к предупреждению. (Ссылка, с другой стороны, удобно возвращает '' для не-ссылок.)
Показать ещё 2 комментария
14

Скаляр всегда содержит один элемент. Все, что находится в скалярной переменной, всегда является скаляром. Ссылка является скалярным значением.

Если вы хотите узнать, является ли эта ссылка, вы можете использовать ref. Если вы хотите узнать ссылочный тип, вы можете использовать процедуру reftype из Scalar:: Util.

Если вы хотите узнать, является ли это объектом, вы можете использовать процедуру blessed из Scalar:: Util. Вы все равно должны заботиться о том, что такое благословенный пакет. UNIVERSAL имеет несколько способов рассказать вам об объекте: если вы хотите проверить, что у него есть метод, который вы хотите вызвать, используйте can; если вы хотите увидеть, что он наследует что-то, используйте isa; и если вы хотите увидеть, что объект обрабатывает роль, используйте DOES.

Если вы хотите знать, действительно ли этот скаляр действует как скаляр, но привязан к классу, попробуйте tied. Если вы получаете объект, продолжите проверку.

Если вы хотите узнать, похоже ли это число, вы можете использовать looks_like_number из Scalar:: Util. Если это не похоже на число, и это не ссылка, это строка. Однако все простые значения могут быть строками.

Если вам нужно сделать что-то более необычное, вы можете использовать модуль, например Params:: Validate.

4

Мне нравится полиморфизм вместо того, чтобы вручную что-то проверять:

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.

обслуживаемой.

2

В какой-то момент я прочитал достаточно убедительный аргумент 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, а не хеш-ссылка.

Мне не нравится эта реализация.

  • 4
    Я, наверное, тот, о ком ты думаешь. Я говорю, никогда не проверяйте буквальные строки. Проверка на прототипы: if (ref $ f eq ref {}). Что касается связи, вы начинаете с tied (): если вы получаете объект, делайте обычные вещи.
  • 2
    Пожалуйста, никогда, никогда не проверяйте рефери таким образом. Это так легко сделать проще. :)
Показать ещё 9 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню