Perlのレキシカルサブルーチンとperlcritic ― 2022年12月21日 02時48分
この記事はPerl Advent Calendar 2022の21日目の分です。
Perlでは、関数内で定義した関数も外部から見えてしまいます。
use feature 'say';
sub foo {
sub bar {
say 'bar';
}
bar();
}
# foo関数の外でもbar関数を呼び出せる。
bar();
特定のスコープでのみ参照できる関数を定義したいときは、関数定義をsub
ではなくmy sub
(またはstate sub
)から始めます。この機能はレキシカルサブルーチン(lexical subroutines)と呼ばれます。
use feature 'say';
sub foo {
my sub bar {
say 'bar';
}
bar();
}
# 未定義の関数呼び出しによる例外が発生する。
bar();
ちょっとした処理をまとめるのに便利なレキシカルサブルーチンですが、perlcritic
との組み合わせに難がありました。レキシカルサブルーチンを使ったコードをperlcritic
にかけると、Subroutines::ProhibitNestedSubs
ポリシーとSubroutines::ProhibitBuiltinHomonyms
ポリシーのエラーが出てしまうのです。(perlcritic
はPerl向けのリンターです。詳しくは「perlcriticとのつきあい方 - 私が歌川です」などを参照してください。)
Subroutines::ProhibitNestedSubs
ポリシーは関数の入れ子を禁止します。入れ子の内側の関数が意図せず外部に公開されるのを避けるためのものなので、もともと外部に公開されないレキシカルサブルーチンに対しては禁止する意味がありません。
Subroutines::ProhibitBuiltinHomonyms
ポリシーは組み込み関数と同名の関数を禁止します。perlcritic
の内部で使われるで使われるPPI
モジュールの不具合により、レキシカルサブルーチンの名前は常にsub
であるとみなされていました。
これらの問題を解決するため、昨年perlcritic
とPPI
に以下のプルリクエストを提出しました。
- Allow lexical subroutines to be inside subroutines by nanto · Pull Request #971 · Perl-Critic/Perl-Critic
- Stop improper violation for lexical subroutines in Subroutines::ProhibitBuiltinHomonyms by nanto · Pull Request #973 · Perl-Critic/Perl-Critic
- Return correct name for lexical subroutines by nanto · Pull Request #261 · Perl-Critic/PPI
この年末までにこれらの変更がすべて取り込まれたので、perlcritic
(とPPI
)のバージョンを最新にすれば、心置きなくレキシカルサブルーチンを使えます。
PerlのText::Markdown::Discountで囲い付きコードブロックを扱う ― 2022年12月11日 01時31分
この記事はPerl Advent Calendar 2022の11日目の分です。
PerlのText::Markdown::Discount
モジュールを使うと、MarkdownをHTMLに変換できます。
use feature qw(say);
use Encode qw(encode_utf8);
use Text::Markdown::Discount qw(markdown);
say encode_utf8 markdown(<<'MARKDOWN');
こんにちは、世界。
* 順序
* なし
* リスト
MARKDOWN
<p>こんにちは、世界。</p>
<ul>
<li>順序</li>
<li>なし</li>
<li>リスト</li>
</ul>
オリジナルのMarkdownにはありませんが、CommonMarkおよびその拡張であるGitHub Flavored Markdownなどには囲い付きコードブロック(fenced code block)が存在します。3つ以上のバッククォート(`
)の並びまたはチルダ(~
)の並びで囲んだ部分が、HTMLのpre
要素とcode
要素を使って出力されるというものです。
Text::Markdown::Discount
モジュール(の内部で使われているDiscountというMarkdown処理系)でも囲い付きコードブロックに対応していますが、扱い方がバージョンによって異なります。
Text::Markdown::Discount
0.14以降
囲い付きコードブロックを有効にするには、markdown
関数の第2引数にMKD_FENCEDCODE
フラグ(0x02000000
)を指定します。
my $html = markdown(<<'MARKDOWN', Text::Markdown::Discount::MKD_NOHEADER | Text::Markdown::Discount::MKD_NOPANTS | 0x02000000);
```
fenced code block
```
MARKDOWN
<pre><code>fenced code block
</code></pre>
MKD_FENCEDCODE
フラグはDiscount処理系で定義されているものの、Text::Markdown::Discount
モジュールでは定数が定義されていていないため、フラグの値である0x02000000
を直接指定しています。(定数を定義するpull requestが提出されています。)
MKD_NOHEADER
フラグおよびMKD_NOPANTS
フラグはmarkdown
関数の第2引数を省略したときにデフォルトで適用されるフラグなので、元の挙動を変えずに別のフラグを追加する際には、このふたつのフラグも明示的に指定する必要があります。
MKD_FENCEDCODE
フラグを指定しない場合、3つ以上のバッククォートの並びはインラインのコード範囲として解釈されます。
my $html = markdown(<<'MARKDOWN');
```
fenced code block
```
MARKDOWN
<p><code>
fenced code block
</code></p>
Text::Markdown::Discount
0.13
特にフラグを指定しなくても、囲い付きコードブロックが有効になっています。
my $html = markdown(<<'MARKDOWN');
```
fenced code block
```
MARKDOWN
<pre><code>fenced code block
</code></pre>
Text::Markdown::Discount
0.12以前
囲い付きコードブロックに対応していません。3つ以上のバッククォートの並びはインラインのコード範囲として解釈されます。
Perlで配列の先頭何要素か以外を抜き出す ― 2022年12月05日 01時47分
この記事はPerl Advent Calendar 2022の5日目の分です。
Perlで配列の先頭n要素以外を抜き出したい——例えば配列('a', 'b', 'c', 'd', 'e')
から先頭2要素以外を抜き出して配列('c', 'd', 'e')
を得たい——とき、最近はList::Util
モジュールのtail
関数を使えます。
tail
関数は配列の末尾n要素を抜き出す関数ですが、抜き出す要素数として負数-mを指定すると、先頭m要素以外の要素を返します。
use List::Util qw(tail);
my @array1 = qw(a b c d e);
my @array2 = tail -2, @array1;
# @array2の内容は('c', 'd', 'e')
List::Util
モジュールはコアモジュール(Perl本体と一緒にインストールされるモジュール)であり、Perl 5.28以降なら追加のモジュールインストールなしにtail
関数を使えます。それより古いPerlでは、List::Util
の新しいバージョン(1.50以降)をインストールする必要があります。
以前からある方法として、配列スライスを使うこともできます。
my @array1 = qw(a b c d e);
my @array2 = @array1[2 .. $#array1];
# @array2の内容は('c', 'd', 'e')
Perlで配列の先頭何要素かを抜き出す ― 2022年12月02日 02時16分
この記事はPerl Advent Calendar 2022の2日目の分です。
Perlで配列の先頭n要素を抜き出したいとき、最近はList::Util
モジュールのhead
関数を使えます。
use List::Util qw(head);
my @array1 = qw(a b c d e);
my @array2 = head 3, @array1;
# @array2の内容は('a', 'b', 'c')
List::Util
モジュールはコアモジュール(Perl本体と一緒にインストールされるモジュール)であり、Perl 5.28以降なら追加のモジュールインストールなしにhead
関数を使えます。それより古いPerlでは、List::Util
の新しいバージョン(1.50以降)をインストールする必要があります。
以前からある方法
配列スライスを使うこともできますが、抜き出す要素数から1引いた値を指定することになって、ちょっと紛らわしいです。
my @array1 = qw(a b c d e);
my @array2 = @array1[0 .. 2];
# @array2の内容は('a', 'b', 'c')
また、元の配列の要素数が抜き出す要素数より少ないときは、不足分がundef
で埋められてしまいます。
my @array1 = qw(a);
my @array2 = @array1[0 .. 2];
# @array2の内容は('a', undef, undef)
undef
で埋められたくなければ、min
関数を使うなどひと工夫する必要があります。
use List::Util qw(min);
my @array1 = qw(a);
my @array2 = @array1[0 .. min(2, $#array1)];
# @array2の内容は('a')
splice
関数を使うこともできますが、元の配列も変更されてしまいます。
my @array1 = qw(a b c d e);
my @array2 = splice @array1, 0, 3;
# @array1の内容は('d', 'e')
# @array2の内容は('a', 'b', 'c')
Perl で関数内に関数を定義する ― 2021年12月20日 21時08分
この記事は Perl アドベントカレンダーの 20 日目の分です。
Perl コードを書いていて、関数内で関数を定義したいと思ったことはありませんか? 普通に sub foo { ... }
の中に sub bar { ... }
を書けばよいのでは思われるかもしれませんが、それだと関数が局所的にならず外部に露出してしまいます。
package MyPackage {
sub foo {
sub bar { 42 }
return bar();
}
}
# foo 関数の外部からも bar 関数を呼び出せる。
say MyPackage::bar(); # => 42
無名関数のコードリファレンスを使うこともできますが、見た目がちょっと煩雑ですね。
package MyPackage {
sub foo {
my $bar = sub { 42 };
return $bar->();
}
}
そこで lexical subroutines の出番です。my sub foo { ... }
または state sub foo { ... }
と書くことで、局所的な関数を定義できます。Perl 5.18 で試験的に導入された機能で、Perl 5.26 から本採用となり警告なしで使えるようになりました。
package MyPackage {
sub foo {
my sub bar { 42 }
return bar();
}
}
# foo 関数の外部からは bar 関数を呼び出せない。
say MyPackage::bar(); # => Undefined subroutine &MyPackage::bar called
lexical subroutines は sort
関数に渡すこともできます。
my sub compare { $b <=> $a }
my $array_1 = [ sort compare 1, 8, 2, 3, 5 ];
my $array_2 = [ sort compare 4, 7, 6, 0, 9 ];
say @$array_1; # => 85321
say @$array_2; # => 97640
注意点として、Perl の lint である Perl-Critic には、バージョン 1.140 時点で lexical subroutines に誤った警告を出してしまうという問題があります。
- ProhibitBuiltinHomonyms misinterprets "my sub" subroutines · Issue #955 · Perl-Critic/Perl-Critic
- Support for local subs with `my sub` · Issue #946 · Perl-Critic/Perl-Critic
筆者はいずれの問題に対しても修正 pull request を提出しています。Perl-Critic の誤検知がなくなり、気兼ねなく lexical subroutines が使えるようになるとよいですね。
最近のコメント