- 前回の復習
- Mojoliciousのインストール
- mapとgrep
- while
- next, last
- 後置if, 後置for
- 前回の復習
- package / 名前空間
- モジュール
- テスト
- テストを使った開発
- 前回の復習問題の「calc.pl」の一部のを解きながら, サブルーチンを復習しましょう.
- 第6回の「Webサービス開発」で利用するWAF, Mojoliciousをインストールしてみましょう
- 予め, plenv等でシステム以外のPerlを用意しておいてください
- 詳しくは, #1-Bの資料で解説しています
$ cpanm Mojolicious
- ...で, 終わりです
- 多少時間がかかります. 暫く待ちましょう
1 distribution installed
と表示されていれば成功です
$ mojo generate lite_app Sample
[exist] /home/fukumoto/sandbox
[write] /home/fukumoto/sandbox/Hoge
[chmod] Hoge 744
- Mojoliciousのテンプレート(雛形)を作ってみます
mojo
はMojoliciousが提供するコマンドです
$ morbo Hoge
[Sun Dec 8 09:38:11 2013] [info] Listening at "http://*:3000".
Server available at http://127.0.0.1:3000.
mojo create
コマンドが生成するHoge
をmorbo
コマンドで実行します- ブラウザに, URLとして「localhost:3000」と入力した際, 「Welcome to the Mojolicious real-time web framework!」と表示されていればOKです!
- 次に,
Hoge
のコードを見てみましょう
#!/usr/bin/env perl
use Mojolicious::Lite;
# Documentation browser under "/perldoc"
plugin 'PODRenderer';
get '/' => sub {
my $self = shift;
$self->render('index');
};
app->start;
__DATA__
@@ index.html.ep
% layout 'default';
% title 'Welcome';
Welcome to the Mojolicious real-time web framework!
@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head><title><%= title %></title></head>
<body><%= content %></body>
</html>
- コード(1)では, 接続したURLに対する処理が書かれています
- コード(2)では, HTMLのテンプレートが書かれています
- Webアプリケーションを開発する際は, このように「見た目の部分」と「処理の部分」を分けて書くことが多いです
- 今は1枚のスクリプトに全て書いていますが, 通常これらは別ファイルに分けて記述します
- 第6回では, いよいよMojoliciousを利用したWebサービスの開発に挑戦します!
- ...が, その前に, 第5回まで紹介できなかったPerlの便利な機能と, テストについて紹介していきたいと思います
- 配列内の値を, 一括で変換する関数です
my @array1 = ( 1 .. 5 );
my @array2 = ();
for my $val (@array1) {
push @array2, $val * 2;
}
print "@array2" # => 2 4 6 8 10
2, 4, 6, 8, 10
という配列を作成するならば, このような手順を踏む必要があります
my @array1 = ( 1 .. 5 );
my @array2 = map { $_ * 2; } @array1;
@array2 = map $_ * 2, @array1;
- 上記のように, 一行で処理することができます
map BLOCK LIST
map { $_ * 2; } @array1;
map EXPR, LIST
map $_ * 2, @array1;
BLOCK
は{}
で囲まれた式の集合}
の後には変換の元となる配列を置きますBLOCK
内における$_
はLIST
における一つ一つの要素を指します
- 返り値は変換後の配列です
- 配列内から条件が真となる値を抽出する関数です
my @array1 = ( 1 .. 10 );
my @array2 = ();
for my $val (@array1) {
if ($val % 2 == 0) {
push @array2, $val; # => 2, 4, 6, 8, 10
}
}
@array1
のうち, 2で割り切れる数を抽出しています
my @array1 = ( 1 .. 10 );
my @array2 = grep { $_ % 2 == 0; } @array1;
@array2 = grep $_ % 2 == 0, @array1;
- 上記のように一行で処理することができます
grep BLOCK LIST
grep { $_ % 2 == 0; } @array1;
grep EXPR, LIST
grep $_ % 2 == 0, @array1;
- 構文は
map
と同様です
my @files = qw/papix.pl moznion.pm macopy.py boolfool.vim/;
上記の配列を引数にして, 拡張子の後ろに .bak
を付け足す map_bak
関数と, 末尾(拡張子)が pl
, pm
であるものを抜き出す grep_pl_and_pm
を作成してください
for
文で繰り返し処理について学びましたが, while
というものもあります
while(my $input = <STDIN>) {
chomp $input;
print "$input\n";
}
()
内の処理(EXPR)が真
である間,{}
内の処理(BLOCK) を繰り返す- 今回の場合, 標準入力に
<C-d>
(Ctrl
キーを押しながらd
キー) が入力されるまで, 標準入力に入力された文字列を出力します
- 今回の場合, 標準入力に
- 今回は使用頻度の高い
next
,last
に関して紹介します
my @languages = qw/c ruby papix perl python java c++/;
for my $lang (@languages) {
if ($lang eq "perl") {
print "Find Perl\n";
next;
}
print "$lang\n";
}
- 試しに上記のコードを実行してみましょう
c
ruby
papix
Find Perl
python
java
c++
- "Find Perl" という文字列は出力されますが,
perl
という文字列は出力されません- これは
next
に到達すると, 残りの処理を行わず, 次のループ($lang[4])
を開始するためです
- これは
my @languages = qw/c ruby papix perl python java c++/;
for my $lang (@languages) {
if ($lang eq "perl") {
print "Find Perl\n";
last;
}
print "$lang\n";
}
- 試しに上記のコードを実行してみましょう
next
をlast
に書き換えるのみです
c
ruby
papix
Find Perl
$lang[3]
のperl
という文字列が一致してif
文内のlast
に到達した時点でfor
ループを抜けます. そのためpython
,java
,c++
という文字列が表示されません
if ($lang eq "perl") {
print "Find Perl\n";
}
for my $foo (1 .. 100) {
print $foo, "\n";
}
- これらのコードには別の書き方があります
print "Find Perl\n" if $lang eq "perl";
- このようにワンライナーで書くことができます
;
の位置に注意してください
print "FizzBuzz\n" if $var % 15;
print "Fizz\n" elsif $var % 3;
print "Buzz\n" elsif $var % 15;
- こういったコードを書くことはできません
- 1行目は問題ありませんが, 2, 3行目で
syntax error
となります
- 1行目は問題ありませんが, 2, 3行目で
print $_ for (1 .. 100);
- リスト内の要素は,
$_
に格納されます
print $val for my $val (1 .. 100);
- こういったコードを書くことはできません
- 引数として与えられた文字列が,
数値A 演算子 数値B
という文字列であれば, その値を計算して, 結果を返すような関数calc_string
を書いてみましょう- 「数値A」は任意の桁の正・負の整数とします. また, 演算子は
+-*/%
が使えるものとします. - 但し, 引数が与えられなかった場合(空の文字列の場合)は, undefを返します
- また,
数値A 演算子 数値B
というフォーマットと一致しない場合もundefを返します
- 「数値A」は任意の桁の正・負の整数とします. また, 演算子は
- 関数
calc_string
とwhile文を使って,Ctrl
キーとd
キーを押すまでの間標準入力から文字列を受け取り, 文字列に書かれた式を計算するようなコードを書いてみましょう
- ある処理を行う
output
サブルーチンがあるとします - でも別の
output
サブルーチンを作りたくなったとします- 処理が少し違うけれど使い方は一緒とか, 互換性を保ちたいとか...
- そこで「XXXXの
output
」, のように, サブルーチンのグループを作る機能です
package Hoge {
sub output {
my $str = shift @_;
print "$str\n";
}
}
- それでは, packageを使っていきましょう
- 渡した文字列に改行を付けて表示してくれる
output
という関数をHoge
というpackageの中に作ってみます
package Hoge {
...
}
- packageを定義する為には,
sub package名 { ... }
と書きます
-
古いPerl(OSに入っているPerlなど)は以下のように書きます
package Hoge; ...
-
この場合, packageの範囲は次のpackage宣言までです
package Hoge {
sub output {
my $str = shift @_;
print "$str\n";
}
}
output('hello, world!'); # packageが違うので使えない!!!!
- packageの外は別のpackageなので, 中のサブルーチン
output
をそのまま使うことは出来ません. - ちなみに何もpackageを宣言していない時は,
main
というpackageに属しています
package Hoge {
sub output {
my $str = shift @_;
print "$str\n";
}
}
Hoge::output("hello, world!"); # => hello, world![改行]
- packageの中のサブルーチンを使いたい時は,
package名::サブルーチン名
で呼び出します
PerlEntrance
というpackageの中にtokyo
とosaka
という名前の, 引数を持たないサブルーチンを作りましょう- tokyoは
papix!!!
, osakaはboolfool!!!
という文字列を返す機能を持たせましょう
use Acme::Nyaa;
use Acme::FizzBuzz;
- 入学式の第1回や, Perl入学式 in YAPCで使いました
- このように処理をひとまとめにして別のところに置いて,
use モジュール名;
で呼び出すものをモジュールと呼びます - モジュールに分けるとファイルが長くなって見通しが悪くなるのを防ぐことが出来ます
- それでは, 自作モジュールを作っていきましょう
作業ディレクトリ
|- plactice.pl # モジュールを実行するスクリプト
|- lib
|- PerlEntrance.pm # モジュール本体
- このような構成でフォルダとファイルを作ります
- Perlのスクリプトの拡張子は
*.pl
ですが, モジュールの場合は*.pm
にします - PerlEntrance.pmの中に, 先ほどの練習問題で書いたコードから, 実行する部分を取り除いたものを書いておきます
- さらにPerlEntrance.pmの末尾に
1;
とだけ書かれた行を追加してください
#!/usr/bin/env perl
use strict;
use warnings;
use PerlEntrance;
print PerlEntrance::tokyo(); # => papix!!!
print PerlEntrance::osaka(); # => boolfool!!!
practice.pl
をこのように書くと, PerlEntrance.pmに書いたサブルーチンを使えるようになります- ただし, 実行時は
perl -Ilib practice.pl
のように起動してください - package名とモジュール名(ファイル名)は, 同じ名前にすることが多いです
PerlBeginners
というモジュール/packageを作ってその中にperllevel
というサブルーチンを作りましょうperllevel
は1から10の整数の引数を取ります.
use PerlBeginners;
print PerlBeginners::perllevel(1);
# =>
# レベル1: Perl 関係の書籍や資料を何も読んでいない。(中略)この言語に合うメンタルモデルを持っていないので、Perl の構文をCOBOL とC++ のような他の言語のものとは区別できていない。
- 引数をレベルと解釈して, 上記のように使います.
- Perlレベルは以下のブログに掲載されているので何らかの形でモジュール内に保持しましょう.
use Test::More;
use PerlEntrance;
is PerlEntrance::tokyo(), 'moznion!!!';
done_testing();
practice.pl
が置いてあるディレクトリに.plactice.t
という名前で以下の様な内容でコードを書きます- そのディレクトリで
prove -Ilib practice.t
と実行してみましょう- どうなりましたか?
- 今回のテストではTest::Moreというモジュールを使います
- 上のテストでは
is
というTest::Moreのサブルーチンを使いましたis <テストしたいサブルーチン> <返ってくるのが期待される値>
のように使いますpapix!!!
と返ってくるのが正解ですが, 今回はあえて失敗させるために別の文字列を使っています
practice.t .. 1/?
# Failed test at practice.t line 5.
# got: 'papix!!!'
# expected: 'moznion!!!'
# Looks like you failed 1 test of 1.
practice.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests
- Test::Moreを使ったテストコードを
prove
というコマンドで分かりやすくテストの状況を表示できますpapix!!!
という文字が返ってきたが, 正しくはmoznion!!!
なので, テストが失敗した, という意味です
- 「エッ!? テストを直すんじゃないの!?」と思うかもしれません
- テストとコード, どちらが正しいかでどちらを直すかを決めますが, 今回はテストが正しいという方針で進めます
- テストコードは直さずに, コードを直してテストを通るようにしてください
All tests successful.
と出たらテストは通っています!
- テストコードから書くこともあれば, コードを書いて手で動作を確認した後に, その手動の確認をテストコードに落とすこともあります
- 自分がわかりやすい方法, 楽な方法でテストは書いていきましょう. 無理して書くものではありません
- 例えばブラウザがないと確認が難しい検証項目をテストコードに落とすのは難しいです. そういうのは手動でやっていくなど使い分けが重要です
- ただし, 人間はミスをするものだし, コストがかかるものです. 自動化できれば助かることは多いはずです.
- 隣の人とペアを作ってぶつかり稽古(ペアプログラミング)をしましょう. 1台のPCで作業を行います
- 以下の機能がある
YAPC
モジュールを実装してください(初級編)- 来年のYAPC::Asiaは2014年8月28日から30日に開催予定です. 日付を教えてくれるモジュールを2人で作りましょう
YAPC::year()
で年を4桁の整数で返します(テストをAの人が, コードをBの人が書きましょう)YAPC::month()
で月を2桁の整数で返します(テストをBの人が, コードをAの人が書きましょう)YAPC::day()
で日付を2桁の整数で返します(テストをAの人が, コードをBの人が書きましょう)
- 以下の機能がある
YAPC
モジュールを実装してください(上級編)YAPC::is_yet(<日付の文字列>)
で, 開催前か開催後かを真か偽で返します(テストをBの人が, コードをAの人が書きましょう)- 「8月28日以前」ならば開催前(真), それ以降なら開催後(偽)として扱うことにします
- 日付のフォーマットは, 「4桁の年/2桁の月/2桁の日」という形にします
- 例えば, 2014年1月1日は, 「2014/01/01」です.
- この上級編はテストもコードも結構難しいです! わからない所があれば, サポーターの人に「どうすればXXXXが出来る?」と聞きいてみましょう
prove -l
とprove -Ilib
は実は同じですok YAPC::is_yet();
でちゃんと真が返ってきているかをテストできます- 偽を調べたい時は
ok !YAPC::is_yet();
と書きます
- 偽を調べたい時は
YAPC::is_yet
は, 引数として日付の文字列を受け取るのではなく, 「プログラムを実行した, 現在の時間」を利用して実装することもできます- 日付をうまく操作するには
Time::Piece
というモジュールを使います - テスト内の時間を操作するには
Test::MockTime
を使ってみてください
- 日付をうまく操作するには