アルパカ三銃士

〜アルパカに酔いしれる獣たちへ捧げる〜

Perl の隠れ演算子の紹介(2017)

この記事は「Perl Advent Calendar 2017」の 25 日目の記事です。
まさか全部埋まるとは思っていませんでした。これも皆さんのおかげ様です。
ありがとうございました!

qiita.com

Perl には隠れ演算子なるものが存在します。
これらは、言語開発者が意図して作ったわけではなく、偶然発見されたものです。それを誰かが命名することで有名になっていきました。
今回はそれらの演算子perlsecret に沿った形でお届けします。
環境は Perl 5.26.0 で Mac OSX Elcapitan です。

隠れ演算子一覧

Venus

0+
+0

Venus 演算子は文字列を数値にキャストしてくれることで有名な演算子です。

print 0+ '23a';                # 23
 
print 0+ '3.00';               # 3
 
print 0+ '1.2e3';              # 1200
 
print 0+ '42 EUR';           # 42
 
print 0+ 'two cents';       # 0
 
$ref = [];
print 0+ $ref, ' ', "$ref";  # 140535297912096 ARRAY(0x7fd0ec82ad20)

Baby cart

@{[ ]}

Larry Wall が 1994 年に見つけたそうです。他にも "shopping-trolley", "pram", "turtle" と呼ぶことがあるそうです。

これは配列リファレンスを文字列として展開ができます。$" 変数に区切り文字を指定することで、文字列展開時にその区切り文字で展開が可能になります。

print "Hello @{ [ 1..10 ] }"    # Hello 1 2 3 4 5 6 7 8 9 10

$" = ','
print "Hello @{ [ 1..10 ] }"    # Hello 1,2,3,4,5,6,7,8,9,10

この演算子使いどころとして他には、次のような処理を行いたい場合です。

for ((1, 2, 3)) {
    $_ = $_ * $_;    # 普通はやらない...
    print "square: $_\n";
}

これはエイリアス $_ の中身を破壊してしまいます。そのため、これを実行すると Modification of a read-only value attempted といった fatal error に引っかかるはずです。
これを回避するために Baby cart を用います。

for (@{[ (1, 2, 3) ]}) {
    $_ = $_ * $_;    # 普通はやらない...
    print "square: $_\n";
}

# square: 1
# square: 4
# square: 9

要するにこの演算子は、配列を一度 [] の中に入れてあげることで配列リファレンスへ変換し、@{}デリファレンスをしてあげるといた動作をしています。

Bang bang

名付け方がそのままですね!

!!

Perl では 0 や空文字、undef は偽となりそれ以外は真として扱われます。
そのため真偽値をはっきりさせるためにこの演算子を用います。この演算子は C プログラマーがよく使っていたそうです。

my $true  = !! 'a string';   # 1
my $false = !! undef;        # ''

Eskimo greeting

}{

Abigail という方が 1997 年に見つけたそうです。別名で "butterfly" とも呼ぶそうです。 これは次のようにワンライナーで主に使われます。なぜなら }{ だけで END ブロックを作成することができるからです。

$ cat somefile.txt | perl -lne 'print$_}{print$.'

左側にループ中の処理を書いて、右側に END ブロック用の処理を書きます。この場合だと入力に与えられた somefile.txt の一行一行を print していき、END ブロック内で入力に与えられた行数を変数 $. から取得して print しています。
詳しい処理内容を調べたい場合は、コマンドラインツールとしてのperl にもあるように -MO=Deparse をつけてみてください!

ここで補足ですが、$. などそもそもこの演算子って何?ってなった場合は、perldoc -v '$.' と入力してみましょう。

Inchworm

~~

これはスマートマッチに似ていますが、scalar() の省略形として扱うことが可能ですが、正確には同じではありません。
Perl には ~ というビット単位演算子が存在します。bang bang 演算子と同じ要領で発明されたらしいですが、bang bang 演算子のように強制的に真偽値を求めるのではなく、何らかの文字列または数値を強制します。

以下のように扱えると幸せになれるでしょう。

$ perl -Esay~~localtime

localtime 単体の状態で呼び出すと配列で返してきます。それを回避するためによく行う手段として scalar localtime といった技を使いますが、上記の方法を使うと短く書けますね!

Inchworm on a stick

~-
-~

Hospel という方が 2002 年に発見したそうです。
この演算子は先にデクリメントを行って数値計算を行います。

my $x = 10;
my $y = ~-$x * 4;   # $y = ($x - 1) * 4 と同じ
print "$y\n";            # 9

これはよくコードゴルフで使われる技らしいです。この演算子の証明もあります。

$x + 1 == - ( -$x ) + 1
 
$x + 1 == - ( ~$x + 1 ) + 1
 
$x + 1 == -~$x - 1 + 1
 
$x + 1 == -~$x

これを見ていて、Perl って演算子を並べることができるのかな?と思ってやってみるとできました...
おったまげー

my $x = +-~~+!1;
print "$x\n";           # 0

my $y = -!1000
print "$y\n";          # 何になるでしょう!?

Space station

-+-

McGlinchy という方が 2005 年に見つけたらしいです。 この演算子0++0 と言った数値へのキャストの演算子と同じ働きをしますが、演算子比較優先順位が高いものとなってるそうです。

# ミス: prints the numification of '20GBP20GBP20GBP'
print 0+ '20GBP' x 3;           # 20
 
# ミス: ( print '20' ) x 3 と等しい
print( 0+ '20GBP' ) x 3;        # 20
 
# 正しい: けど長い
print( ( 0 + '20GBP' ) x 3 );   # 202020
 
# 正しい: space station operator を使う
print -+- '20GBP' x 3;          # 202020

しかし、この演算子は数値から始まっていない文字列に対しては期待しない動作を振舞います。

print -+- 'two cents';  # +two cents
 
print -+- '-2B';            # -2B

print 0 + '-2B'             # -2

Goatse

=( )=

別名 "Saturn" らしいです!(かっこいい!!) 名前の意味はかなり下品ですが、とても便利な演算子です!

説明するよりも使い方を見た方が早いです。

# $_ の単語のカウント
$n =()= /word1|word2|word3/g;
 
# $n = 1
$n =()= "abababab" =~ /a/;
 
# $n = 4
$n =()= "abababab" =~ /a/g;

# $n = 4; $b = 'a'
$n =($b)= "abababab" =~ /a/g;
 
# $n = 4; @c = qw( a a a a )
$n =(@c)= "abababab" =~ /a/g;

凄い!!便利!!ですが、あんまり使う機会がなさそうです。
説明元にもこういう場合で使用すると便利だよっていう例が載っていました。

# 空の配列リファレンスを削除したい
@arrays = ( [], [1], [ 1, 2 ], [], [ 5 .. 9 ] );
 
# @filled = ( [1], [ 1, 2 ], [ 5 .. 9 ] );
@filled = grep +()= @$_, @arrays;

Flaming X-Wing

=<>=~

Philippe Bruhat が 2007 年に見つけたらしいです。 入力用のファイルハンドルである <> から読み込んだデータの各行を正規表現でマッチし、その部分を左辺の変数に代入するという演算子です。

$ cat main.go | perl -E 'my @d=<>=~/package main/g;say"@d"'

Kite

~~<>

Philippe Bruhat が 2012 年に発見したそうです。別名で "sperm" です(確かに似てる)。
これは inchworm 演算子と diamond 演算子を組み合わせたもので、もし入力がからであった場合は undef の代わりに空文字を返すとのことですが、 diamond 演算子単体と何が違うのかわかりませんでした...(誰か違いを教えてください)

Ornate double-bladed sword

<<m=~m>>
m
;

2013 年に Abigail が作成したとのこと。
複数行のコメントアウトを行うことができます。試しに次のコードをコピーして perl -cシンタックスをチェックしてみてください。

#!/usr/bin/env perl

use strict;
use warnings;

<<m=~m>>
  Use the secret operator on the previous line.
  Put your comments here.
  Lots and lots of comments.
 
  You can even use blank lines.
  Finish with a single
m
;

実際には void コンテキストでダブルクォートで囲まれた文字列らしいです。

Screwdriver operators

2007 年に Dmitry Karasik が ! をベースに探したことで発見されました。
Screwdriver 演算子は条件付き演算子です。メジャーなものとして以下のものに分かれるそうです。

  • Flathead
-=!!
-=!

この場合条件によってデクリメントを行います。

$x -=!! $y     # $x-- if $y;
$x -=!  $y     # $x-- unless $y;
  • Phillips
+=!!
+=!

この場合条件によってインクリメントを行います。

$x +=!! $y;    # $x++ if $y;
$x +=!  $y;    # $x++ unless $y;
  • Torx
*=!!
*=!

この場合は条件によって 0 でリセットします。

$x *=!! $y;    # $x = 0 unless $y;
$x *=!  $y;    # $x = 0 if $y;

Torx screwdriver 演算子Perl 5.13.5以下で -1 ではない負の数では正しく動作しないらしいです。また、5.7.0以下のバージョンでは -1 で失敗するとのこと。

  • Pozidriv
x=!!
x=!

この場合は条件によって空文字としてリセットします。

$x x=!! $y;    # $x = '' unless $y;
$x x=!  $y;    # $x = '' if $y;

Pozidriv screwdriver 演算子は Philippe Bruhat が 2009 年に隠れ演算子についてトークをしようと準備している時に見つけたとのこと。

Winking fat comma

またもや Abigail が 2010 年に見つけたとのこと。別名 "grappling hook"。

以下のように扱います。

use constant APPLE   =>  1;
use constant CHERRY  =>  2;
use constant BANANA  =>  3;
 
%hash = (
  APPLE   ,=>  "green",
  CHERRY  ,=>  "red",
  BANANA  ,=>  "yellow",
);

これは次と等価です。

%hash = ( 1, "green", 2, "red", 3, "yellow" );

Enterprise

PerlMonks 上で Aristotle と名乗っている方が 2006 年に発見したとのこと。 (他にも "NCC-1701", "snail" といった呼び方も)
これは非常に便利です。例えば次のコードがあります。

my @shopping_list = ('bread', 'milk');
push @shopping_list, 'apples'   if $cupboard{apples} < 2;
push @shopping_list, 'bananas'  if $cupboard{bananas} < 2;
push @shopping_list, 'cherries' if $cupboard{cherries} < 20;
push @shopping_list, 'tonic'    if $cupboard{gin};

初期化した後に条件的に push していくといった方法ですが、Enterprise 演算子を使うと、宣言時に条件に合わせて配列に値をセットしていくことが可能になります。

my @shopping_list = (
    'bread',
    'milk',
   ('apples'   )x!! ( $cupboard{apples} < 2 ),
   ('bananas'  )x!! ( $cupboard{bananas} < 2 ),
   ('cherries' )x!! ( $cupboard{cherries} < 20 ),
   ('tonic'    )x!! $cupboard{gin},
);

これは上記の bang bang 演算子を組み合わせており、条件が真なら 1, 偽なら 0 となるため、その数を x 演算子で評価するといったことで実現しています。発想力が凄いですね!!

Key to the truth

2013 年に Toby Inkster によって発見されたそうです。
venus 演算子と bang bang 演算子を組み合わせることで実現しています。真偽値を 1 もしくは 0 で表現します。

my $true  = 0+!! 'a string';    # now 1
my $false = 0+!! undef;         # now 0

Serpent of truth

2014 年に Daniel Bruder によって提案されました。
見ての通り Inchworm 演算子と bang bang 演算子を組み合わせることで実現しています。これは Key to the truth 演算子と同じです。

my $true  = ~~!! 'a string';    # now 1
my $false = ~~!! undef;         # now 0

Abbott and Costello

Yves Orton によって発見されました。

||()

条件に応じてリストをセット返します。

 my @shopping_list = ('bread', 'milk', 1 || ('a', 'b'), 0 || ('c', 'd'), 'apples') 
# 'bread', 'milk', 1, 'c', 'd', 'apples'

Leaning Abbott and Costello

//()

2014 年に Damien Krotkine によって提案されました。
Abbott and Costello 演算子とほぼ同じですが、undef の場合にリストを返します。

my @shopping_list = ('bread', 'milk', 1//('a', 'b'), 0//('c', 'd'), undef//('e', 'f'), 'apples')
# 'bread', 'milk', 1, 0, 'e', 'f', 'apples'

隠れコンスタント一覧

コンスタントもあるらしいです...

Space fleet

<=><=><=>

Damian Conway によって発見されました。
3 つの Spaceship 演算子に見えますが、端の 2 つは glob("=") と等価です。このコンスタントは 0 を持っています。

Amphisbaena

<~>

2009 年に Rafaël Garcia-Suarez によって発見されました。
ホームディレクトリのパスを持っています。

最後に

いかがでしたか?
途中から面倒になってきましたが、演算子同士を組み合わせることができるということも知れたのでいい経験でした。

宣伝ですが、2018 年 3 月 3 日に沖縄で YAPC::Okinawa を開催します。
そろそろ応募締め切るので出来る限り早めの参加お願いします!

yapcjapan.org