6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-28

#kichijojipm で、 lodash.js を Perl5 に移植している話をした際、songmu さんから、次のような指摘をもらった

少し咀嚼してみる

_ とは何か

  • -r, -x などのファイルテスト演算子を実行すると、演算対象となったファイルハンドルをキャッシュする
  • _ とは、そのキャッシュされたファイルハンドル
    • stat cache と呼ぶこともある
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 のように、 stat の挙動を変更できる

ここでは filetest pragma で access(2) を呼び、-r -w -x -R -W -X の挙動を変更できることについて書く

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

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

% 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 では キャッシュする仕組みを通していない
  • これは、将来、解消される予定
  • キャッシュをしていないかどうか確かめるには、 -r -w -x -R -W -X しか置き換えないことを利用して、それ以外のファイルテストでキャッシュさせ、-r -w -x -R -W -Xを利用しても、キャッシュが置き換わってないことを確かめればよい。次がその例:
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 以降であれば、
次のように、ファイルテストを複数実施することができ、これは直感に反しない結果になる
ので、基本的に、これを利用すれば良いと思う

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;

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

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

まず、記号だけで関数定義を探ってみる
perldata を読むと、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).

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

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;

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

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 で見たことありましたが、::だけで定義できて、パッケージの区切りと混乱する感じが面白いですね(?)

: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

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?