Perlのレキシカルサブルーチンとperlcritic2022年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であるとみなされていました。

これらの問題を解決するため、昨年perlcriticPPIに以下のプルリクエストを提出しました。

この年末までにこれらの変更がすべて取り込まれたので、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 subroutinessort 関数に渡すこともできます。

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 に誤った警告を出してしまうという問題があります。

筆者はいずれの問題に対しても修正 pull request を提出しています。Perl-Critic の誤検知がなくなり、気兼ねなく lexical subroutines が使えるようになるとよいですね。