perldsc
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- СПИСКИ ССЫЛОК
- ОБЩИЕ ОШИБКИ
- ЗАМЕЧАНИЕ О ПРЕИМУЩЕСТВЕ
- ПОЧЕМУ ВЫ ДОЛЖНЫ ВСЕГДА ИСПОЛЬЗОВАТЬ strict
- ОТЛАДКА
- ПРИМЕРЫ КОДА
- МАССИВЫ МАССИВОВ
- ХЭШИ МАССИВОВ
- МАССИВЫ ХЭШЕЙ
- ХЭШИ ХЭШЕЙ
- БОЛЕЕ СЛОЖНЫЕ СТРУКТУРЫ
- Связи с базой данных
- СМОТРИТЕ ТАКЖЕ
- АВТОР
ИМЯ
perldsc - Справочник по структурам данных Perl
ОПИСАНИЕ
Perl позволяет использовать сложные структуры данных. Вы можете написать что-то вроде этого, и вдруг у вас будет массив с тремя измерениями!
for my $x (1 .. 10) {
for my $y (1 .. 10) {
for my $z (1 .. 10) {
$AoA[$x][$y][$z] =
$x ** $y + $z;
}
}
} Однако, как бы просто это ни выглядело, на самом деле это гораздо более сложная конструкция, чем кажется!
Как его вывести на печать? Почему нельзя просто сказать print @AoA? Как его отсортировать? Как передать его в функцию или получить его из функции? Это объект? Можно ли сохранить его на диск для последующего чтения? Как получить целые строки или столбцы этой матрицы? Все значения должны быть числовыми?
Как видите, легко запутаться. Некоторая вина за это может быть возложена на реализацию на основе ссылок, но в большей степени это связано с отсутствием документации с примерами, предназначенными для начинающих.
Этот документ призван предоставить подробное, но понятное изложение различных типов структур данных, которые вы можете захотеть разработать. Он также должен служить справочником примеров. Таким образом, когда вам нужно создать одну из этих сложных структур данных, вы можете просто взять пример из этого руководства.
Давайте рассмотрим каждый из этих возможных конструктов подробно. Есть отдельные разделы для следующих пунктов:
-
массивы массивов
-
хэши массивов
-
массивы хэшей
-
хэши хэшей
-
более сложные конструкции
Но сейчас давайте рассмотрим общие проблемы, характерные для всех этих типов структур данных.
СПИСКИ ССЫЛОК
Самое важное, что нужно понять о всех структурах данных в Perl, включая многомерные массивы, заключается в том, что, хотя они могут выглядеть иначе, массивы и хэши Perl @ARRAY и %HASH являются одномерными. Они могут хранить только скалярные значения (строки, числа или ссылки). Они не могут напрямую содержать другие массивы или хэши, а вместо этого содержат ссылки на другие массивы или хэши.
Вы не можете использовать ссылку на массив или хэш так же, как вы используете настоящий массив или хэш. Для программистов, знакомых с C или C++, которые не привыкли различать массивы и указатели на них, это может быть непонятно. Если это так, просто подумайте о разнице между структурой и указателем на структуру.
Вы можете (и должны) узнать больше о ссылках в perlref. Вкратце, ссылки похожи на указатели, которые знают, на что они указывают. (Объекты также являются типом ссылок, но нам они не понадобятся прямо сейчас — если вообще понадобятся.) Это означает, что когда у вас есть то, что выглядит как доступ к двухмерному или многомерному массиву и/или хэшу, на самом деле происходит следующее: базовый тип — это просто одномерная сущность, содержащая ссылки на следующий уровень. Просто вы можете использовать его так, как будто это двухмерный.
$array[7][12] # array of arrays
$array[7]{string} # array of hashes
$hash{string}[7] # hash of arrays
$hash{string}{'another string'} # hash of hashes Теперь, поскольку верхний уровень содержит только ссылки, если вы попытаетесь вывести свой массив с помощью простой функции print(), вы получите не очень приятный результат, например:
my @AoA = ( [2, 3], [4, 5, 7], [0] );
print $AoA[1][2];
7
print @AoA;
ARRAY(0x83c38)ARRAY(0x8b194)ARRAY(0x8b1d0) Это потому, что Perl не (никогда) неявно разыменовывает ваши переменные. Если вы хотите получить то, на что ссылается ссылка, вам нужно сделать это самостоятельно, используя префиксные указатели, такие как ${$blah}, @{$blah}, @{$blah[$i]}, или же постфиксные стрелки, такие как $a->[3], $h->{fred}, или даже $ob->method()->[3].
ОБЩИЕ ОШИБКИ
Две наиболее частые ошибки при создании массива массивов заключаются либо в неверном подсчете элементов, либо в повторном использовании ссылки на один и тот же адрес памяти. Вот случай, когда вы просто получаете количество, а не вложенный массив:
for my $i (1..10) {
my @array = somefunc($i);
$AoA[$i] = @array; # WRONG!
} Это просто случай присваивания массива скаляру и получения его количества элементов. Если это именно то, что вам нужно, то вы можете сделать это более явно, например:
for my $i (1..10) {
my @array = somefunc($i);
$counts[$i] = scalar @array;
} Вот случай, когда вы повторно используете ссылку на один и тот же адрес памяти:
# Either without strict or having an outer-scope my @array;
# declaration.
for my $i (1..10) {
@array = somefunc($i);
$AoA[$i] = \@array; # WRONG!
} В чем же проблема? Это выглядит правильно, не так ли? Ведь я только что сказал вам, что вам нужен массив ссылок, так что вы и сделали мне такой!
К сожалению, хотя это и верно, всё равно не работает. Все ссылки в @AoA ссылаются на одно и то же место, и поэтому все они будут содержать последнее значение в @array! Это похоже на проблему, продемонстрированную в следующей программе на C:
#include <pwd.h>
main() {
struct passwd *getpwnam(), *rp, *dp;
rp = getpwnam("root");
dp = getpwnam("daemon");
printf("daemon name is %s\nroot name is %s\n",
dp->pw_name, rp->pw_name);
} Что выведет:
daemon name is daemon
root name is daemon Проблема в том, что как rp, так и dp — указатели на одно и то же место в памяти! В C вам нужно было бы помнить о malloc(). В Perl вам следует использовать конструктор массивов [] или конструктор хэшей {} вместо этого. Вот правильный способ выполнения предыдущего фрагмента кода:
# Either without strict or having an outer-scope my @array;
# declaration.
for my $i (1..10) {
@array = somefunc($i);
$AoA[$i] = [ @array ];
} Квадратные скобки создают ссылку на новый массив с копией содержимого @array на момент присваивания. Это то, что вам нужно.
Обратите внимание, что это даст результат, похожий на:
# Either without strict or having an outer-scope my @array;
# declaration.
for my $i (1..10) {
@array = 0 .. $i;
$AoA[$i]->@* = @array;
} Это то же самое? Возможно, да, а возможно и нет. Тонкая разница в том, что при присваивании в квадратных скобках вы точно знаете, что это всегда новая ссылка с новой копией данных. Что-то еще может происходить в этом новом случае с разыменованием $AoA[$i]->@* в левой части присваивания. Всё зависит от того, была ли $AoA[$i] не определена изначально или она уже содержала ссылку. Если вы уже заполнили @AoA ссылками, как в
$AoA[3] = \@another_array; Тогда присваивание с разыменованием в левой части присваивания использует уже существующую ссылку:
$AoA[3]->@* = @array; Конечно, это будет иметь «интересный» эффект уничтожения @another_array. (Вы замечали, как когда программист говорит, что что-то «интересно», это скорее не значит «завораживающе», а скорее беспокоит тем, что это «раздражает», «сложно» или и то, и другое? :-)
Итак, всегда используйте конструкторы массивов или хэшей с [] или {}, и всё будет в порядке, хотя это не всегда оптимально эффективно.
Удивительно, но следующий, на первый взгляд, опасный фрагмент кода на самом деле будет работать:
for my $i (1..10) {
my @array = somefunc($i);
$AoA[$i] = \@array;
} Это потому, что my() больше похоже на оператор выполнения времени, чем на оператор объявления компиляции per se. Это означает, что переменная my() создаётся заново каждый раз при проходе цикла. Таким образом, даже если кажется, что вы сохранили одну и ту же ссылку на переменную каждый раз, вы этого не делаете! Это тонкое различие может привести к более эффективному коду за счёт введения в заблуждение всех, кроме самых опытных программистов. Поэтому я обычно не советую это начинающим. В действительности, за исключением передачи аргументов в функции, я редко рекомендую использовать оператор получения ссылки (обратный слэш) в коде. Вместо этого я рекомендую начинающим (и большинству из нас), пытаться использовать гораздо более понятные конструкторы [] и {} вместо того, чтобы полагаться на лексическое (или динамическое) область действия и скрытое подсчёт ссылок, чтобы выполнить правильные действия за кулисами.
Также обратите внимание, что существует и другой способ записи разыменования! Эти две строки эквивалентны:
$AoA[$i]->@* = @array;
@{ $AoA[$i] } = @array; Первая форма, называемая постфиксным разыменованием, обычно легче читается, поскольку выражение можно читать слева направо, и нет вложенных фигурных скобок для балансировки. С другой стороны, она также является более новой. Она была добавлена в язык в 2014 году, поэтому в старом коде вы часто встретите другую форму, окружающее разыменование.
В итоге:
$AoA[$i] = [ @array ]; # usually best
$AoA[$i] = \@array; # perilous; just how my() was that array?
$AoA[$i]->@* = @array; # way too tricky for most programmers
@{ $AoA[$i] } = @array; # just as tricky, and also harder to read ЗАМЕЧАНИЕ О ПРЕИМУЩЕСТВЕ
Говоря о таких вещах, как @{$AoA[$i]}, следующие на самом деле являются одним и тем же:
$aref->[2][2] # clear
$$aref[2][2] # confusing Это потому, что правила приоритета Perl для пяти префиксных операторов разыменования (которые выглядят как ругательства: $ @ * % &) заставляют их связываться сильнее, чем постфиксные скобки или фигурные скобки индексации! Это, несомненно, станет большим сюрпризом для программиста C или C++, который привык использовать *a[i] для обозначения значения, указываемого i-м элементом a. То есть они сначала берут индекс, а затем разыменовывают значение по этому индексу. В C это нормально, но здесь не C.
Подобная конструкция в Perl, $$aref[$i] сначала выполняет разыменование $aref, превращая его в ссылку на массив, а затем разыменовывает эту ссылку и, наконец, возвращает i-е значение массива, на который указывает $AoA. Если вам нужна концепция C, вы можете написать $AoA[$i]->$* для явного разыменования i-го элемента, читая слева направо.
ПОЧЕМУ ВЫ ДОЛЖНЫ ВСЕГДА use strict
Если это звучит пугающе, успокойтесь. Perl имеет некоторые функции, которые помогут вам избежать наиболее распространённых ловушек. Лучший способ избежать путаницы — начать каждый скрипт с:
use strict; Таким образом, вы будете вынуждены объявлять все свои переменные с my() и запретите случайное «символьное разыменование». Поэтому, если вы сделали так:
my $aref = [
[ "fred", "barney", "pebbles", "bambam", "dino", ],
[ "homer", "bart", "marge", "maggie", ],
[ "george", "jane", "elroy", "judy", ],
];
print $aref[2][2]; Компилятор немедленно отметит это как ошибку во время компиляции, так как вы случайно обратились к @aref, не объявленной переменной, и напомнит вам написать вместо этого:
print $aref->[2][2] ОТЛАДКА
Вы можете использовать команду отладчика x для вывода сложных структур данных. Например, учитывая присваивание $AoA выше, вот вывод отладчика:
DB<1> x $AoA
$AoA = ARRAY(0x13b5a0)
0 ARRAY(0x1f0a24)
0 'fred'
1 'barney'
2 'pebbles'
3 'bambam'
4 'dino'
1 ARRAY(0x13b558)
0 'homer'
1 'bart'
2 'marge'
3 'maggie'
2 ARRAY(0x13b540)
0 'george'
1 'jane'
2 'elroy'
3 'judy' ПРИМЕРЫ КОДА
Ниже представлены короткие примеры кода, иллюстрирующие доступ к различным типам структур данных, без комментариев.
МАССИВЫ МАССИВОВ
Объявление МАССИВА МАССИВОВ
@AoA = (
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
); Генерация МАССИВА МАССИВОВ
# reading from file
while ( <> ) {
push @AoA, [ split ];
}
# calling a function
for $i ( 1 .. 10 ) {
$AoA[$i] = [ somefunc($i) ];
}
# using temp vars
for $i ( 1 .. 10 ) {
@tmp = somefunc($i);
$AoA[$i] = [ @tmp ];
}
# add to an existing row
push $AoA[0]->@*, "wilma", "betty"; Доступ и вывод МАССИВА МАССИВОВ
# one element
$AoA[0][0] = "Fred";
# another element
$AoA[1][1] =~ s/(\w)/\u$1/;
# print the whole thing with refs
for $aref ( @AoA ) {
print "\t [ @$aref ],\n";
}
# print the whole thing with indices
for $i ( 0 .. $#AoA ) {
print "\t [ $AoA[$i]->@* ],\n";
}
# print the whole thing one at a time
for $i ( 0 .. $#AoA ) {
for $j ( 0 .. $AoA[$i]->$#* ) {
print "elem at ($i, $j) is $AoA[$i][$j]\n";
}
} ХЭШИ МАССИВОВ
Объявление ХЭША МАССИВОВ
%HoA = (
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane", "elroy" ],
simpsons => [ "homer", "marge", "bart" ],
); Генерация ХЭША МАССИВОВ
# reading from file
# flintstones: fred barney wilma dino
while ( <> ) {
next unless s/^(.*?):\s*//;
$HoA{$1} = [ split ];
}
# reading from file; more temps
# flintstones: fred barney wilma dino
while ( $line = <> ) {
($who, $rest) = split /:\s*/, $line, 2;
@fields = split ' ', $rest;
$HoA{$who} = [ @fields ];
}
# calling a function that returns a list
for $group ( "simpsons", "jetsons", "flintstones" ) {
$HoA{$group} = [ get_family($group) ];
}
# likewise, but using temps
for $group ( "simpsons", "jetsons", "flintstones" ) {
@members = get_family($group);
$HoA{$group} = [ @members ];
}
# append new members to an existing family
push $HoA{flintstones}->@*, "wilma", "betty"; Доступ и вывод ХЭША МАССИВОВ
# one element
$HoA{flintstones}[0] = "Fred";
# another element
$HoA{simpsons}[1] =~ s/(\w)/\u$1/;
# print the whole thing
foreach $family ( keys %HoA ) {
print "$family: $HoA{$family}->@* \n"
}
# print the whole thing with indices
foreach $family ( keys %HoA ) {
print "family: ";
foreach $i ( 0 .. $HoA{$family}->$#* ) {
print " $i = $HoA{$family}[$i]";
}
print "\n";
}
# print the whole thing sorted by number of members
foreach $family ( sort { $HoA{$b}->@* <=> $HoA{$a}->@* } keys %HoA ) {
print "$family: $HoA{$family}->@* \n"
}
# print the whole thing sorted by number of members and name
foreach $family ( sort {
$HoA{$b}->@* <=> $HoA{$a}->@*
||
$a cmp $b
} keys %HoA )
{
print "$family: ", join(", ", sort $HoA{$family}->@* ), "\n";
} МАССИВЫ ХЭШЕЙ
Объявление МАССИВА ХЭШЕЙ
@AoH = (
{
Lead => "fred",
Friend => "barney",
},
{
Lead => "george",
Wife => "jane",
Son => "elroy",
},
{
Lead => "homer",
Wife => "marge",
Son => "bart",
}
); Генерация МАССИВА ХЭШЕЙ
# reading from file
# format: LEAD=fred FRIEND=barney
while ( <> ) {
$rec = {};
for $field ( split ) {
($key, $value) = split /=/, $field;
$rec->{$key} = $value;
}
push @AoH, $rec;
}
# reading from file
# format: LEAD=fred FRIEND=barney
# no temp
while ( <> ) {
push @AoH, { split /[\s+=]/ };
}
# calling a function that returns a key/value pair list, like
# "lead","fred","daughter","pebbles"
while ( %fields = getnextpairset() ) {
push @AoH, { %fields };
}
# likewise, but using no temp vars
while (<>) {
push @AoH, { parsepairs($_) };
}
# add key/value to an element
$AoH[0]{pet} = "dino";
$AoH[2]{pet} = "santa's little helper"; Доступ и вывод МАССИВА ХЭШЕЙ
# one element
$AoH[0]{lead} = "fred";
# another element
$AoH[1]{lead} =~ s/(\w)/\u$1/;
# print the whole thing with refs
for $href ( @AoH ) {
print "{ ";
for $role ( keys %$href ) {
print "$role=$href->{$role} ";
}
print "}\n";
}
# print the whole thing with indices
for $i ( 0 .. $#AoH ) {
print "$i is { ";
for $role ( keys $AoH[$i]->%* ) {
print "$role=$AoH[$i]{$role} ";
}
print "}\n";
}
# print the whole thing one at a time
for $i ( 0 .. $#AoH ) {
for $role ( keys $AoH[$i]->%* ) {
print "elem at ($i, $role) is $AoH[$i]{$role}\n";
}
} ХЭШИ ХЭШЕЙ
Объявление ХЭША ХЭШЕЙ
%HoH = (
flintstones => {
lead => "fred",
pal => "barney",
},
jetsons => {
lead => "george",
wife => "jane",
"his boy" => "elroy",
},
simpsons => {
lead => "homer",
wife => "marge",
kid => "bart",
},
); Генерация ХЭША ХЭШЕЙ
# reading from file
# flintstones: lead=fred pal=barney wife=wilma pet=dino
while ( <> ) {
next unless s/^(.*?):\s*//;
$who = $1;
for $field ( split ) {
($key, $value) = split /=/, $field;
$HoH{$who}{$key} = $value;
}
# reading from file; more temps
while ( <> ) {
next unless s/^(.*?):\s*//;
$who = $1;
$rec = {};
$HoH{$who} = $rec;
for $field ( split ) {
($key, $value) = split /=/, $field;
$rec->{$key} = $value;
}
}
# calling a function that returns a key,value hash
for $group ( "simpsons", "jetsons", "flintstones" ) {
$HoH{$group} = { get_family($group) };
}
# likewise, but using temps
for $group ( "simpsons", "jetsons", "flintstones" ) {
%members = get_family($group);
$HoH{$group} = { %members };
}
# append new members to an existing family
%new_folks = (
wife => "wilma",
pet => "dino",
);
for $what (keys %new_folks) {
$HoH{flintstones}{$what} = $new_folks{$what};
} Доступ и вывод ХЭША ХЭШЕЙ
# one element
$HoH{flintstones}{wife} = "wilma";
# another element
$HoH{simpsons}{lead} =~ s/(\w)/\u$1/;
# print the whole thing
foreach $family ( keys %HoH ) {
print "$family: { ";
for $role ( keys $HoH{$family}->%* ) {
print "$role=$HoH{$family}{$role} ";
}
print "}\n";
}
# print the whole thing somewhat sorted
foreach $family ( sort keys %HoH ) {
print "$family: { ";
for $role ( sort keys $HoH{$family}->%* ) {
print "$role=$HoH{$family}{$role} ";
}
print "}\n";
}
# print the whole thing sorted by number of members
foreach $family ( sort { $HoH{$b}->%* <=> $HoH{$a}->%* } keys %HoH ) {
print "$family: { ";
for $role ( sort keys $HoH{$family}->%* ) {
print "$role=$HoH{$family}{$role} ";
}
print "}\n";
}
# establish a sort order (rank) for each role
$i = 0;
for ( qw(lead wife son daughter pal pet) ) { $rank{$_} = ++$i }
# now print the whole thing sorted by number of members
foreach $family ( sort { $HoH{$b}->%* <=> $HoH{$a}->%* } keys %HoH ) {
print "$family: { ";
# and print these according to rank order
for $role ( sort { $rank{$a} <=> $rank{$b} }
keys $HoH{$family}->%* )
{
print "$role=$HoH{$family}{$role} ";
}
print "}\n";
} БОЛЕЕ СЛОЖНЫЕ ЗАПИСИ
Объявление БОЛЕЕ СЛОЖНЫХ ЗАПИСЕЙ
Вот пример, показывающий, как создать и использовать запись, поля которой имеют разные типы:
$rec = {
TEXT => $string,
SEQUENCE => [ @old_values ],
LOOKUP => { %some_table },
THATCODE => \&some_function,
THISCODE => sub { $_[0] ** $_[1] },
HANDLE => \*STDOUT,
};
print $rec->{TEXT};
print $rec->{SEQUENCE}[0];
$last = pop $rec->{SEQUENCE}->@*;
print $rec->{LOOKUP}{"key"};
($first_k, $first_v) = each $rec->{LOOKUP}->%*;
$answer = $rec->{THATCODE}->($arg);
$answer = $rec->{THISCODE}->($arg1, $arg2);
# careful of extra block braces on fh ref
print { $rec->{HANDLE} } "a string\n";
use FileHandle;
$rec->{HANDLE}->autoflush(1);
$rec->{HANDLE}->print(" a string\n"); Объявление ХЭША СЛОЖНЫХ ЗАПИСЕЙ
%TV = (
flintstones => {
series => "flintstones",
nights => [ qw(monday thursday friday) ],
members => [
{ name => "fred", role => "lead", age => 36, },
{ name => "wilma", role => "wife", age => 31, },
{ name => "pebbles", role => "kid", age => 4, },
],
},
jetsons => {
series => "jetsons",
nights => [ qw(wednesday saturday) ],
members => [
{ name => "george", role => "lead", age => 41, },
{ name => "jane", role => "wife", age => 39, },
{ name => "elroy", role => "kid", age => 9, },
],
},
simpsons => {
series => "simpsons",
nights => [ qw(monday) ],
members => [
{ name => "homer", role => "lead", age => 34, },
{ name => "marge", role => "wife", age => 37, },
{ name => "bart", role => "kid", age => 11, },
],
},
); Генерация ХЭША СЛОЖНЫХ ЗАПИСЕЙ
# reading from file
# this is most easily done by having the file itself be
# in the raw data format as shown above. perl is happy
# to parse complex data structures if declared as data, so
# sometimes it's easiest to do that
# here's a piece by piece build up
$rec = {};
$rec->{series} = "flintstones";
$rec->{nights} = [ find_days() ];
@members = ();
# assume this file in field=value syntax
while (<>) {
%fields = split /[\s=]+/;
push @members, { %fields };
}
$rec->{members} = [ @members ];
# now remember the whole thing
$TV{ $rec->{series} } = $rec;
###########################################################
# now, you might want to make interesting extra fields that
# include pointers back into the same data structure so if
# change one piece, it changes everywhere, like for example
# if you wanted a {kids} field that was a reference
# to an array of the kids' records without having duplicate
# records and thus update problems.
###########################################################
foreach $family (keys %TV) {
$rec = $TV{$family}; # temp pointer
@kids = ();
for $person ( $rec->{members}->@* ) {
if ($person->{role} =~ /kid|son|daughter/) {
push @kids, $person;
}
}
# REMEMBER: $rec and $TV{$family} point to same data!!
$rec->{kids} = [ @kids ];
}
# you copied the array, but the array itself contains pointers
# to uncopied objects. this means that if you make bart get
# older via
$TV{simpsons}{kids}[0]{age}++;
# then this would also change in
print $TV{simpsons}{members}[2]{age};
# because $TV{simpsons}{kids}[0] and $TV{simpsons}{members}[2]
# both point to the same underlying anonymous hash table
# print the whole thing
foreach $family ( keys %TV ) {
print "the $family";
print " is on during $TV{$family}{nights}->@*\n";
print "its members are:\n";
for $who ( $TV{$family}{members}->@* ) {
print " $who->{name} ($who->{role}), age $who->{age}\n";
}
print "it turns out that $TV{$family}{lead} has ";
print scalar ( $TV{$family}{kids}->@* ), " kids named ";
print join (", ", map { $_->{name} } $TV{$family}{kids}->@* );
print "\n";
} Связь с базой данных
Вы не можете легко связать многоуровневую структуру данных (такую как хэш хэшей) с файлом dbm. Первая проблема заключается в том, что у всех, кроме GDBM и Berkeley DB, есть ограничения на размер, но помимо этого у вас также есть проблемы с тем, как ссылки должны быть представлены на диске. Один экспериментальный модуль, который частично пытается решить эту проблему, — модуль MLDBM. Проверьте ближайший сайт CPAN, как описано в perlmodlib, для исходного кода MLDBM.
См. также
perlref, perllol, perldata, perlobj
АВТОР
Tom Christiansen <tchrist@perl.com>
© 1993–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.34.0/perldsc