特殊ファイルハンドル _ と、記号での関数定義

[#kichijojipm](https://kichijojipm.connpass.com/) で、 lodash.js を Perl5 に移植している話をした際、songmu さんから、次のような指摘をもらった

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">perlでアンダースコアは実は組み込みで使われてるんだよな <a href="https://twitter.com/hashtag/kichijojipm?src=hash&amp;ref_src=twsrc%5Etfw">#kichijojipm</a></p>&mdash; songmu (@songmu) <a href="https://twitter.com/songmu/status/999988730143363077?ref_src=twsrc%5Etfw">2018年5月25日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">アンダースコアは特殊変数(?)としてデフォルトてま使われています <a href="https://twitter.com/hashtag/kichijojipm?src=hash&amp;ref_src=twsrc%5Etfw">#kichijojipm</a> / “ファイルテスト演算子をつかいまくる” <a href="https://t.co/siK5ZYkkRF">https://t.co/siK5ZYkkRF</a></p>&mdash; songmu (@songmu) <a href="https://twitter.com/songmu/status/999994657571459072?ref_src=twsrc%5Etfw">2018年5月25日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


少し咀嚼してみる

# `_` とは何か

- `-r`, `-x` などのファイルテスト演算子を実行すると、演算対象となったファイルハンドルをキャッシュする
- `_` とは、そのキャッシュされたファイルハンドル
  - `stat cache` と呼ぶこともある

```perl:example
-f $file && -r $file && -x $file;

# use _
-f $file && -r _ && -x _;

# stack filetest from perl5.10
-f -r -x $file
```

# `filetest` pragma での注意

OS によっては、ファイルテストをコントロールしたい場合、
例えば、[BSD::stat](https://metacpan.org/pod/BSD::stat) のように、 `stat` の挙動を変更できる

ここでは [filetest](https://metacpan.org/pod/filetest) pragma で `access(2)` を呼び、`-r -w -x -R -W -X` の挙動を変更できることについて書く

```perl:example-filetest-pragma.pl
$file = 'example.pl';
-r $file;
{
    use filetest 'access';
    -r $file;
}
-r $file;
```

system call を観察してみると、次のように `stat64`, `access` が呼び出されてることがわかる

```shell
% sudo dtruss  -f sudo -u $(id -u -n) 'perl example-filetest-pragma.pl' 2>&1 | grep example.pl
39296/0x117f576:  stat64("example.pl\0", 0x103987C80, 0x0)               = -1 Err#2
39296/0x117f576:  access("example.pl\0", 0x4, 0x0)               = -1 Err#2
39296/0x117f576:  stat64("example.pl\0", 0x103987C80, 0x0)               = -1 Err#2
```


- ただし、現時点 perl5.26 だと、 `filetest` pragma では キャッシュする仕組みを通していない 
- これは、将来、解消される予定
  - https://metacpan.org/pod/filetest#Limitation-with-regard-to-_
- キャッシュをしていないかどうか確かめるには、 `-r -w -x -R -W -X` しか置き換えないことを利用して、それ以外のファイルテストでキャッシュさせ、`-r -w -x -R -W -X`を利用しても、キャッシュが置き換わってないことを確かめればよい。次がその例：

```perl:notice-filest-pragma
use strict;
use warnings;

use Test::More;

subtest 'default' => sub {
    ok -d '/etc';
    my @stat_etc = stat(_);
    ok not -w '/etc/passwd';
    ok -f _;
    my @stat = stat(_);
    ok not $stat[1] == $stat_etc[1];
};

subtest 'use filetest pragma' => sub {
    use filetest 'access';
    ok -d '/etc';
    my @stat_etc = stat(_);

    ok not -w '/etc/passwd';
    ok not -f _; # /etc/passwd is not file ??????????
    my @stat = stat(_);
    is_deeply \@stat_etc, \@stat; # _ is /etc !!!!!!!!!
};

done_testing;
```

長々と書いたが、
perl5.10 以降であれば、
次のように、ファイルテストを複数実施することができ、これは直感に反しない結果になる
ので、基本的に、これを利用すれば良いと思う

```perl
use strict;
use warnings;

use Test::More;

subtest 'stack' => sub {
    ok -d '/etc';
    ok not -f -w '/etc/passwd';
};

subtest 'stack with filetest pragma' => sub {
    use filetest 'access';
    ok -d '/etc';
    ok not -f -w '/etc/passwd';
};

done_testing;
```

# 余談: `_` を関数名として利用できる件について

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">っていうかPerl, 普通にunderscoreだけのサブルーチン定義出来るのな... <a href="https://twitter.com/hashtag/kichijojipm?src=hash&amp;ref_src=twsrc%5Etfw">#kichijojipm</a></p>&mdash; (HTTP)Sの執行者 (@__papix__) <a href="https://twitter.com/__papix__/status/999987375030788097?ref_src=twsrc%5Etfw">2018年5月25日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

次を見るとわかるように、不思議な挙動をするのでさらに不思議!!

まず、記号だけで関数定義を探ってみる
[perldata](http://perldoc.perl.org/perldata.html#Variable-names) を読むと、`underscore, ::, '` あたりが使えそうなことがわかる

> Usually this name is a single identifier, that is, a string beginning with a letter or underscore, and containing letters, underscores, and digits. In some cases, it may be a chain of identifiers, separated by :: (or by the slightly archaic '); all but the last are interpreted as names of packages, to locate the namespace in which to look up the final identifier (see Packages in perlmod for details).

色々手探りで探すと、↓↓ のような関数定義はできることがわかる

```perl
use strict;
use warnings;
use Test::More;

sub _ { 'call _' }
sub :: { 'call ::' }
sub _'_ { "call _'_" }

is _, 'call _';
is ::, 'call ::';
is _'_, "call _'_";

ok __PACKAGE__->can('_');
TODO: {
    local $TODO = 'can("::") does not return coderef of ::';
    ok not __PACKAGE__->can("::");
}
ok __PACKAGE__->can("_'_");

done_testing;
```

さらに、クラス呼び出しを試してみると・・

```perl
use strict;
use warnings FATAL => 'all';

package Foo {
    sub _ { 'call _' }
    sub :: { 'call ::' }
    sub _'_ { "call _'_" }
}

package main;

use Test::More;

subtest '_' => sub {
    eval "Foo::_";
    like $@, qr/Bareword "Foo::_" not allowed while "strict subs"/;

    eval "Foo->_";
    like $@, qr/Can't locate object method "_" via package "Foo"/;

    ok not (Foo->can('_'));
};

subtest '::' => sub {
    eval "Foo::::";
    like $@, qr/Bareword "Foo::::" refers to nonexistent package/;

    eval "Foo->::";
    like $@, qr/Bareword found where operator expected/;

    ok not (Foo->can('::'));
};

subtest "_'_" => sub {
    eval "Foo::_'_";

    # "Foo::_'_" ではなく、 Foo::_::_ !!
    like $@, qr/Bareword "Foo::_::_" not allowed while "strict subs"/;

    eval "Foo->_'_";
    ok not $@;

    ok(Foo->can("_'_"));
};

done_testing;
```

なかなか興味深い

事例があるか調べると・・ `_'_` はあった（まじか）
https://grep.metacpan.org/search?qci=&q=_%27_&qft=&qd=perl&f=t%2Fjaph%2Fabigail.t

`::`が関数の一部に使われている例は、[Import::Into](https://metacpan.org/pod/Import::Into#$package-%3Eimport::into(-$target,-@arguments-);) で見たことありましたが、`::`だけで定義できて、パッケージの区切りと混乱する感じが面白いですね(?)


:smile: 

# 最後に

吉祥寺pm 楽しかったです! magnolia_k_ さん機会をありがとうございました!

# 宣伝 :tada:

6/11(月) に Gotanda.pm を六本木で開催するので、ぜひ来て欲しいです!
kazeburo さんの System Programming and Perl の話が楽しみでしょうがない..!!!!!!!!!

参加登録はこちらから↓
https://gotanda-pm.connpass.com/event/89459/

# 参考

http://perldoc.perl.org/functions/-X.html
http://perldoc.perl.org/functions/stat.html
https://metacpan.org/pod/filetest#Limitation-with-regard-to-_
http://perl-users.jp/articles/advent-calendar/2008/06.html
http://blog.livedoor.jp/dankogai/archives/51049254.html