Extra: Perlブートキャンプ
https://gyazo.com/0d1bf4a5eb563d48d18c2d7a36009a44
Perl
Perl(パール)とは、ラリー・ウォールによって開発されたプログラミング言語である。 実用性と多様性を重視しており、C言語やsed、awk、シェルスクリプトなど他のプログラミング言語の優れた機能を取り入れている。 ウェブ・アプリケーション、システム管理、テキスト処理などのプログラムを書くのに広く用いられている。
素早いウェブ開発の道具として、古くから人気の Lightweight Language
はてな、DeNA、mixi、livedoor (LINE)、…
ISUCONでも人気言語
はてなでも多くのサービスで使われていて、いまでも使われています
Go と違って、コンパイルをしないインタプリタ型言語
Better shell としても使えるし、ウェブアプリケーションを書くことだってできる
最新は 5.30
Perl5 っていう言語。Perl 5.x と Perl6 は全然違う
Java と JavaScript 程度には似てる
ゴール
2時間でPerlのすべてを理解する :575:
周りのコードを参考にしながらPerlが書けるようになる
インストール
code:インストール
% brew install plenv
% ❌ brew install perl-build # 🙅♀️
% git clone git://github.com/tokuhirom/Perl-Build.git $(plenv root)/plugins/perl-build/
% echo 'eval "$(plenv init -)"' >> ~/.zshrc
code:新しいシェルで…
% plenv install 5.28.1
% plenv global 5.28.1 # デフォルトで 5.28.1 を使う
% plenv versions
system
5.24.0
5.24.1
* 5.28.1 (set by /Users/motemen/.plenv/version)
5.8.8
% perl -v
This is perl 5, version 28, subversion 1 (v5.28.1) built for darwin-2level
...
実行してみよう
code:bash
% perl -e 'print 1+1, "\n"'
2
ドキュメントは perldoc で引く
code:bash
% perldoc Carton # モジュール(後述)のドキュメント
% perldoc -f print # 組み込み関数は -f
% perldoc -v @_ # 組み込み変数は -v
% perldoc perl
CPAN
The Comprehensive Perl Archive Network
世界中のさまざまなPerlモジュールが集まっている
Perlの言語的な強みの一つ=コミュニティ
CPANを使う
code:cpanminus自体のインストール
% plenv install-cpanm
...
% cpanm -V
cpanm (App::cpanminus) version 1.7044 (/Users/motemen/.plenv/versions/5.28.1/bin/cpanm)
...
code:cpanm
% cpanm Carton # グローバルにインストール
Carton
Ruby でいう Bundler みたいなもの
cpanfile に書いたモジュール@バージョンを、ローカルのディレクトリ local/ にインストールしてくれる
チームでは Docker 使うのであまり問題にはならないかも
Perlという言語
だいたいこんな感じです
code:lib/Example/Greeter.pm
package Example::Greeter;
use strict;
use warnings;
sub greet {
my ($class, $what) = @_;
print "hello, $what\n";
}
1;
code:main.pl
use strict;
use warnings;
use lib 'lib';
use Example::Greeter;
Example::Greeter->greet('world');
code:bash
% perl -Ilib main.pl
hello, world
順番にみていきましょう……
おまじない: use strict; use warnings;
もともと better awk だったころの名残で素の Perl はかなり自由
use strict; すると、my で宣言されていない変数があると静的にエラー
詳しくは: perldoc strict perldoc warnings
変数
Perl の変数は、そのデータ型に応じてシジルと呼ばれる記号が先頭につく
静的な型はない
$ @ %
$: スカラ
1つの値
数字も文字列もぜんぶスカラ!
code:perl
my $scalar1 = 'test';
my $scalar2 = 1000;
my $scalar3 = \@array; # リファレンス(後述)
0 や ""(空文字列)は falsy な値として扱われる
💣 "0" も falsy であることに注意!
$calar とおぼえるらしい
@: 配列
code:perl
my @array = ('a', 'b', 'c');
@rray とおぼえるらしい
配列要素へのアクセス
code:perl
配列のいち要素はスカラなので、アクセスするときは $array[1] になる
$array と @array は別物である!
配列の操作
code:perl
push @array, 'meow'; # 最後に要素を追加
my $v = shift @array; # 最初の要素を取り除いて返す
my $length = scalar @array; # 長さ
for my $e (@array) { # 全要素ループ
print $e;
}
perldoc -f push shift pop unshift
配列の操作 (2)
code:perl
my @doubles = map { $_ * 2 } @numbers; # すべてを2倍にした配列を作る
my @over20 = grep { $_ > 20 } @numbers; # 20より大きな数だけを集めた配列を作る
$_ は "それ" 的な特別な変数
perldoc -f map grep
手元で試してみよう
code:example-array.pl
use strict;
use warnings;
use Data::Dumper;
my @array = qw(foo bar baz);
print Dumper(\@array);
push @array, 'qux';
print Dumper(\@array);
shift @array;
print Dumper(\@array);
for my $v (@array) {
print "- $v\n";
}
%: ハッシュ
いわゆるディクショナリやマップ
code:perl
my %hash = (
perl => 'larry',
ruby => 'matz',
);
=> は "fat comma" と呼ばれ、( perl => 'larry' ) は ( 'perl', 'larry' ) と同一
右辺は配列のときと同様なリストだが、どんな変数に格納するかでアクセスの仕方が変わる
%ash だそうです 🤔
ハッシュの要素へのアクセス
code:perl
print $hash{'perl'};
ハッシュのいち要素はスカラなので、アクセスするときのシジルは $ になる。もう知ってるね。
配列とちがって {} を使う
キーである文字列に記号などが含まれない場合はクォートを省略できて $hash{perl} と書くのが普通です
ハッシュの操作
code:perl
$hash{perl}; # get
$hash{perl} = 'larry'; # set
for my $key (keys %hash) { # 全要素のキー
my $value = $hash{$key}; # キーで各要素を get
}
perldoc -f keys exists delete
手元で試してみよう
code:example-hash.pl
use strict;
use warnings;
use Data::Dumper;
my %hash = (
foo => 1,
bar => 2,
);
print Dumper(\%hash);
$hash{baz} = 3;
print Dumper(\%hash);
delete $hash{foo};
print Dumper(\%hash);
for my $key (keys %hash) {
print "- $key: $hash{$key}\n";
}
undef
スカラ変数の初期値
いわゆる undefined とか nil とか
code:perl
my $x;
print $x; # "Use of uninitialized value $x in print"
defined 組み込み関数で undef かどうかチェックできる
正規表現
Perlの強みのひとつ。めちゃ強力
/.../ で正規表現リテラルを生成
=~ で文字列にマッチさせる
code:perl
my ($id) = ("こんにちは、id:motemen です" =~ /id:(.+)/); # 文字列マッチで一部を抜き出す
my @lines = split /\n/, $text; # 入力を行に分割
perldoc perlre
ほか
if 文とかは想像通りのものがだいたいある
else if はなくて elsif
ループの制御構文
continue = next, break = last
文字列の比較は == などではなく eq
否定は ne
perldoc perlop
文字列の結合は .
qw(foo bar baz) は ('foo', 'bar', 'baz') の糖衣構文
perldoc -f qw
リファレンス
とても重要
リファレンス=スカラ/配列/ハッシュなどへの参照
Perlの配列やハッシュはかなり素朴
複雑なデータ構造を作るとき、必ず必要になる
行列を作ってみるか…
こんなかんじやろ:
code:bad.pl
my @matrix = (
(0, 1, 2, 3),
(4, 5, 6, 7),
);
実際にはこれと同じ:
code:bad.pl
my @matrix =
(0, 1, 2, 3, 4, 5, 6, 7);
Perlのリストは入れ子にできない!
そこでリファレンス
code:perl
my @array = ('a', 'b', 'c');
my $ref = \@array; # @array へのリファレンス
リストのデータをスカラに閉じ込められました!
code:good.pl
my @matrix = (
);
2つの配列へのリファレンスをもった(長さ2の)配列ができました!
配列リファレンスへのアクセス
code:perl
my @array2 = @$ref; # 頭に @ をつけて配列に戻す
print $ref->1; # -> を使って直接アクセス ハッシュリファレンス
同様に…
code:perl
my %hash = (
perl => 'larry',
ruby => 'matz',
);
my $ref = \%hash;
# 略記
my $ref = {
perl => 'larry',
ruby => 'matz',
};
アクセスしよう
code:perl
my $ref = {
perl => 'larry',
ruby => 'matz',
};
my @keys = keys %$ref; # % を頭につけてデリファレンス
print $ref->{perl}; # -> で直接アクセス
まとめ
$var: スカラ、@var: 配列、%var: ハッシュ。それぞれ別の変数
\ で変数のリファレンスを取る
perldoc perldata
おまけ: +{ ... } という何か
とりあえずそういうのあるんやと思ったら忘れていいです
code:perl
+{
a => 1,
b => 2,
};
この + って何!?
Perl においては { ... } はブロックという構文要素でもあり、場合によってはハッシュリファレンスを作ったつもりがブロックとして扱われる場合がある
ことを明示的に回避するためのもの
おまけ: デリファレンスいろいろ
code:perl
my $a_of_a = [
];
なにかの式をデリファレンスするときは @{ ... } とか %{ ... } で囲むぞ。
code:perl
Postfix dereference というやつ。motemen は使ったことないぞ。ヤバイですね!
おまけ: データをダンプする
データの構造がわからなくなったらダンプしてデバッグしよう
code:perl
use strict;
use warnings;
use Data::Dumper;
print Dumper(\%hash);
# $VAR1 = {
# 'foo' => [
# 1,
# 2,
# 3
# ]
# };
Dumper() にはスカラを渡す。配列やハッシュを渡したい場合はリファレンスにしよう
演習
配列リファレンスを作ってダンプしてみよう。
ハッシュリファレンスをダンプしてみよう。
なんかネストした構造を作ってダンプしてみよう。
上級: [1, 2, 3] と \(1, 2, 3) の違いを見てみよう……。
⏳このへんで半分くらい
interlude: REPL
code:bash
% cpanm Reply
% plenv rehash
% reply
関数
code:perl
sub foo {
}
で関数(サブルーチン)が宣言できるぞ!
code:perl
foo(1, 2, 3);
で関数呼び出しできるぞ! 普通だな!
引数なしの関数呼び出しはカッコを省略できる:
code:perl
foo;
引数の受け取り方/値の返し方
code:perl
sub add {
my ($x, $y) = @_;
return $x + $y;
}
my $three = add(1, 2);
add(1, 2) と呼び出したときの引数は @_ という特殊な配列に格納されるぞ。
これを my ($x, $y) に分割代入してるわけだな。
ECMAScript では 2015 で入った機能だが、Perl 4 の時点で対応してたみたいだ。1990 年代。
返り値は return で返せる。省略もできるが、意図が読みづらくなるので書いたほうがいい。
代入以外の受け取り方
引数なしの shift は暗黙に @_ を引数に取るので…
my $arg1 = shift; する書き方も多い。
@_ は配列なので…
my $arg1 = $_[0]; で最初の引数にアクセスすることもできるぞ。
引数処理イディオム
code:perl
sub func1 {
my ($arg1, $arg2, %args) = @_;
my $opt1 = $args{opt1};
my $opt2 = $args{opt2};
}
func1('hoge', 'fuga', opt1 => 1, opt2 => 2);
最後に %args と受けることで、(省略可能な)名前付き引数を実現できる。
コンテキスト
Perlの難しいところのひとつ。
式を評価するさい、スカラコンテキストとリストコンテキストとがある。
code:例.pl
my @array = (10, 20, 30);
my @x = @array; # この @array はリストコンテキストで評価される
my $y = @array; # この @array はスカラコンテキストで評価される
my ($z) = @array; # この @array はリストコンテキストで評価される
@x が (10, 20, 30) になるのはまあ自然だ。
$y はどうなる……?
$y == 3
配列をスカラコンテキストで評価するとその長さを返す ⭐イディオムだ。
$z は先に見た @_ パターンと一緒で、$z == 10。配列の残りは捨てられる。
リストが自然に扱えて嬉しかったりします。
Go でやるみたいに値とエラーを一緒に返したりもできるといえばできる。
呼び出された関数の側からは、wantarray 関数でコンテキストを知ることができる。
コンテキストのよくある罠
なんかユーザから送られた入力を受けとる関数 param() があったとして、
code:罠.pl
my $in = {
name => param('key') # この関数呼び出しはリストコンテキストで評価される
};
param() がリストコンテキストで配列を返すことになっていたら……(ありがち)
code:罠.pl
my $in = {
name => ('foo', 'bar', 'baz')
};
と評価され……
code:罠.pl
my $in = {
name => 'foo',
bar => 'baz',
};
したみたいになり、bar という不思議なキーが生え、不可解なバグの原因になったりします。
\毎年恒例/ コンテキストクイズ
code:perl
sort <ここ>;
length <ここ>;
if (<ここ>) { }
for my $i (<ここ>) { }
$obj->method(<ここ>);
my $x = <ここ>;
my ($x) = <ここ>;
my @y = <ここ>;
my %hash = (
key0 => 'hoge',
key1 => <ここ>,
);
scalar(<ここ>);
<ここ>;
モジュールシステム
パッケージ
code:perl
package Example::Greeter;
と書くと、以降のソースコードは Example::Greeter という名前空間に所属する。
パッケージは関数の名前が所属する先、と思っていればいい。
code:perl
package A;
sub foo { ... }
package B;
sub bar { ... }
というコードが与えられると、A::foo() B::bar() という名前でそれぞれの関数を参照できるぞ。
概要
基本的には以下のような機構
use Foo::Bar::Baz
lib/Foo/Bar/Baz.pm が読み込まれ、
Foo::Bar::Baz 名前空間が作られる
Perl の実行ファイルは .pl、モジュールは .pm 拡張子
なので実務で書くのはほとんど .pm だぞ。
use したときのファイルの探索先は、@INC というグローバルな特殊変数に格納されている。
慣習的に、プロジェクト直下の ./lib を @INC に入れるように構成されているぞ。
@INC を直接操作したり、perl -Ilib で起動したり。
perldoc -f use perldoc -v @INC
典型的なモジュール
code:lib/Foo/Bar/Baz.pm
package Foo::Bar::Baz;
use strict;
use warnings;
# このパッケージの外からは Foo::Bar::Baz::public_function でアクセスできるぞ
sub public_function {
...
}
# 非公開な気分のメソッドは _ ではじめるぞ。気分だけなので外からアクセスもできちゃうが。
sub _private_function {
...
}
# モジュールの最後にはかならず真値を記す。そうしないと use が失敗するぞ。
1;
関数のエクスポート
code:perl
# コアモジュールではないので cpanm JSON でインストールする
use JSON 'encode_json';
print JSON::encode_json({ a => 1 }); # => {"a":1}
print encode_json({ a => 1 }); # 修飾なしで使える
ざっくり言うと use MODULE 'foo', 'bar'...; でモジュールに生えている関数をインポートできる
ほんとうはもっと汎用的で複雑だけど、この理解であまり困らないぞ。
use の引数なしでも勝手にインポートしてくれるモジュールもあるぞ。
Data::Dumper や JSON もそういうやつ
perldoc perlmod perldoc Exporter
オブジェクト指向
ここまでやれば免許皆伝だ!
Perlはもともとオブジェクト指向言語として始まったわけではないので、あとづけ
とはいえ普通に使えます
先述のパッケージとリファレンスがわかっていれば、ほぼカバーできている
オブジェクト指向とは?
簡単に言えばデータ構造と手続きを一緒にしたもの
オブジェクトはクラスから生成される
オブジェクトはデータを持つ
クラスにはデータに対する手続き(メソッド)が定義されている
table:PerlにおけるOOP
クラス パッケージ
メソッド パッケージに定義された関数
オブジェクト パッケージに bless() されたリファレンス
bless
bless がオブジェクト指向Perlにおける最後の登場人物。
code:bless.pl
my $data = { name => 'motemen' }; # なんかデータがある(普通はハッシュリファレンス)
my $self = bless $data, 'Hatena::Engineer'; # データにパッケージを紐付けると
$self->tweet(); # メソッドが呼べる! ここでは Hatena::Engineer::tweet
code:図解
いいか、みんな
(゚д゚ )
(| y |)
リファレンスと package では手続き型プログラミングしかできないが
{} ( ゚д゚) package
\/| y |\/
二つ合わさればOOPとなる
( ゚д゚) bless
(\/\/
OK、じゃあクラスを作ってみよう。
コンストラクタ
コンストラクタも自分で書く。普通は new という名前を使う。
code:lib/Person.pm
package Person;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
return bless \%args, $class;
}
使い方:
code:perl
my $person = Person->new(age => 18);
# { age => 18 } が Person に bless されたもの
Person->new(age => 18) は Person::new('Person', age => 18) の糖衣構文だぞ。こうやってクラスメソッドが定義できる。
ちなみに bless されていてもハッシュリファレンスであることに代わりはないので $person->{age} で内臓にアクセス可能。
メソッド
ついにメソッドだ! そろそろゴールしてもいいだろう。
code:lib/Person.pm
sub age {
my ($self) = @_;
return $self->{age};
}
sub incr_age {
my ($self) = @_;
$self->{age}++;
}
使い方:
code:perl
$person->incr_age;
$person->incr_age は、$person の bless 先である Person をたどって、結局 Person::incr_age($person) と同様。こうやってインスタンスメソッドが定義できる。
メソッド呼び出しまとめ
code:perl
# この二つが等価
Class->method($arg1, $arg2);
Class::method('Class', $arg1, $arg2);
# この二つが等価
$object->method($arg1, $arg2);
Class::method($object, $arg1, $arg2);
継承
なんか継承ってやつもOOPにはあるらしいよ!
parent.pm を使う。
code:lib/Animal.pm
package Animal;
use strict;
use warnings;
sub new { ... }
sub walk { ... }
1;
code:lib/Dog.pm
package Dog;
use strict;
use warnings;
use parent 'Animal';
1;
code:perl
my $dog = Dog->new();
$dog->walk(); # Animal::walk が呼ばれる
オブジェクト指向まとめ
手作り感あふれるオブジェクト指向
パッケージに手続きを定義
bless でデータと結びつける
コンストラクタは自分でつくる、オブジェクトも自分で作る
オブジェクト指向風に呼び出せるような糖衣
(省略しましたが)オブジェクト指向に必要な機能はそろっている
便利なCPANモジュールがあります
クラスビルダーと呼ぶ。Class::Accessor::Lite がちょうどいい軽さかな。
code:lib/Foo.pm
package Foo;
use strict;
use warnings;
use Class::Accessor::Lite (
new => 1,
);
1;
code:perl
my $foo = Foo->new(bar => 1); # new できてる
$fop->bar; # bar メソッドも生えてる
ちなみに CPAN には
なんとか::Lite
なんとか::Simple
なんとか::Easy
みたいなモジュールが100万個くらいあります
テスト
テストを書くエコシステムもきちんと整っているぞ。
prove ってテスト実行用のコマンドが同梱
テストは t/ ディレクトリ以下に置く
拡張子は .t (中身はPerl)
code:t/Person.t (Perl)
use strict;
use warnings;
use Test::More;
use Person;
my $person = Person->new(age => 18);
is $person->age, 18, '最初は18歳';
$person->incr_age;
is $person->age, 19, 'incr_age 呼んだら19歳';
done_testing;
code:bash
% prove -Ilib -v t/Person.t
t/Person.t ..
ok 1 - 最初は18歳
ok 2 - incr_age 呼んだら19歳
1..2
ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs ( 0.01 usr 0.01 sys + 0.04 cusr 0.00 csys = 0.06 CPU)
Result: PASS
Test::More は is, is_deeply などのテスト用関数をエクスポートする
これらを呼び出すテストファイル(Perlスクリプト)を prove で実行するのがPerlのテスト
Test::Class
テストをクラスで書く。xUnit 的なやつ。これを使ってるチームも多い
code:t/Person.t (Perl)
package t::Person;
use strict;
use warnings;
use parent 'Test::Class';
sub setup : Test(setup) { # なにか不思議な魔法で関数名のあとにアノーテーションできる
# 各テスト前のセットアップ(変数をクリアしたりとか)
}
sub incr_age : Tests {
is ...;
}
補遺: utf8 フラグについて