Как я могу сказать, какой тип значения в переменной Perl? - PullRequest
55 голосов
/ 13 ноября 2009

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

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

Ответы [ 5 ]

52 голосов
/ 13 ноября 2009

ref () :

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

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

43 голосов
/ 13 ноября 2009

$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") { ...). Также см. полиморфизм .

18 голосов
/ 13 ноября 2009

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

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

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

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

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

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

4 голосов
/ 15 ноября 2009

Мне нравится полиморфизм вместо ручной проверки чего-либо:

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 голосов
/ 13 ноября 2009

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

Мне нравится идея проверить, действует ли скаляр как как хеш-реф, а не как хэш-реф.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...