Perl入学式2013年度第5回補講・参加レポート vol.7(packege/モジュール, テスト, 最終問題)

昨日ふと、今月末の第6回補講に向けて予習記事を書いてみたりしましたが、
http://note103.hateblo.jp/entry/2014/03/23/002233

その勢いで以前に書き進めたまま中断してしまった第5回の内容をまとめておきたいと思います。

その前までの復習記事でMojolicousの導入の他、map, grep, next, last, 後置if/for, whileなどの落穂ひろい的な内容を触れ終えたところで、
http://note103.hateblo.jp/entry/2014/02/18/184136

とはいえ、というかじつはというか第5回の本題的なところはここからです。

資料的には以下からです。
https://github.com/perl-entrance-org/workshop-2013-05/blob/master/slide.md#package--%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93

といっても、ここで教わったことについては資料を順に読んでいく&話を聞いていくよりない感じもあるので、そのまま練習問題をどう解いたか、という話に行きたいと思います。

package 'PerlEntrance.pm'

最初の練習問題はこれですね。
https://github.com/perl-entrance-org/workshop-2013-05/blob/master/slide.md#%E7%B7%B4%E7%BF%92%E5%95%8F%E9%A1%8C-2

  • PerlEntranceというpackageの中にtokyoとosakaという名前の, 引数を持たないサブルーチンを作りましょう
  • tokyoはpapix!!!, osakaはboolfool!!!という文字列を返す機能を持たせましょう

ということで、今考えながら書いてみますが、こんな感じでしょうか。

#!/usr/bin/env perl
use strict;
use warnings;

package PerlEntrance {
    sub tokyo {
        return "papix!!!"
    }
    sub osaka {
        return "boolfool!!!"
    }
}

print PerlEntrance::tokyo()."\n";
print PerlEntrance::osaka()."\n";

このまま、講座のときに撮った動画で確認してみます。
・・確認中・・
あってました。では次。

PerlBiginners::perllevel

問題はこちら。
https://github.com/perl-entrance-org/workshop-2013-05/blob/master/slide.md#%E7%B7%B4%E7%BF%92%E5%95%8F%E9%A1%8C-3

  • PerlBeginnersというモジュール/packageを作ってその中にperllevelというサブルーチンを作りましょう
  • perllevelは1から10の整数の引数を取ります.
use PerlBeginners;
print PerlBeginners::perllevel(1);
# =>
# レベル1: Perl 関係の書籍や資料を何も読んでいない。(中略)この言語に合うメンタルモデルを持っていないので、Perl の構文をCOBOL とC++ のような他の言語のものとは区別できていない。

ということで、さっそく作ってみます。まずは「PerlBeginners.pm」として以下を作ります。

#!/usr/bin/env perl
use strict;
use warnings;

package PerlBeginners {
    sub perllevel {
    my $str = shift;
    if ($str ==1) {
            return "レベル1: Perl 関係の書籍や資料を何も読んでいない。Perl がプログラミング言語だということは知っているが、それ以外のことは何も知らない。他人の書いたPerl プログラムを実行できるので、プログラムの一部を編集することでプログラムの動作の一部(出力される文字列の内容など)を変更できることを知っている。プログラムのほかの部分に変更を加えてもなぜうまくいかないのか理解していない。この言語に合うメンタルモデルを持っていないので、Perl の構文をCOBOL とC++ のような他の言語のものとは区別できていない。";
        } elsif ($str == 2) {
            return "レベル2: 基本ブロック構造の構文を理解しているが、JavaScript のような言語に似ているという程度の認識にとどまっている。ブロックがある種のスコープ効果を生じさせるという理解はあるものの、レキシカル変数のことは知らないし、use strict や use warnings に出会ったこともない。条件の意味を変更することができ、基本的な算術演算子と論理演算子条件を使用できる。必要とするすべてのことを、他人の書いたプログラムに若干の変更を加えることで達成できると思っている。"
 #中略
        } elsif ($str == 10) {
            return "レベル10: Perl のobfuscation コンテストや「golf」コンテスト*3 に参加する。たとえば、普通のプログラマなら1 つのプログラムを丸ごと必要とするような関数を、埋め込みコードを使って単一の正規表現で実装してしまう。Perl コアにパッチを送ったり、新しいメジャーなモジュールを寄贈したりすることで、Perl コミュニティで有名人になるかもしれない。"
        }
    }
}

1;

ちょっと長いので中略しました。
これを実行する「practice.pl」はこちらで。

#!/usr/bin/env perl
use strict;
use warnings;
use PerlBeginners;

print PerlBeginners::perllevel(1)."\n";

ではこれも解答と突き合わせてみます。
・・確認中・・
ちなみにこのpackage編に入ってからはまこぴー先生のレクチャーに切り替わっています。(それまではpapix校長)
・・確認中・・
なるほど・・確認しましたが、上記の回答でもアリではあるものの、別の考え方としてハッシュリファレンスを使ったこのようなものもあるとのこと。(PerlBeginners.pm)

#!/usr/bin/env perl
use strict;
use warnings;

package PerlBeginners {
    sub perllever {
    my $str = shift;
    my $levellist = {
            1 => "レベル1: Perl 関係の書籍や資料を何も読んでいない。Perl がプログラミング言語だということは知っているが、それ以外のことは何も知らない。他人の書いたPerl プログラムを実行できるので、プログラムの一部を編集することでプログラムの動作の一部(出力される文字列の内容など)を変更できることを知っている。プログラムのほかの部分に変更を加えてもなぜうまくいかないのか理解していない。この言語に合うメンタルモデルを持っていないので、Perl の構文をCOBOL とC++ のような他の言語のものとは区別できていない。",
            2 => "レベル2: 基本ブロック構造の構文を理解しているが、JavaScript のような言語に似ているという程度の認識にとどまっている。ブロックがある種のスコープ効果を生じさせるという理解はあるものの、レキシカル変数のことは知らないし、use strict や use warnings に出会ったこともない。条件の意味を変更することができ、基本的な算術演算子と論理演算子条件を使用できる。必要とするすべてのことを、他人の書いたプログラムに若干の変更を加えることで達成できると思っている。",
#後略
            };
        return $levellist->{$str};
    }
}

1;

なるほど・・と言う感じで、これなら10回「return」って書かなくてよいですし、何よりリファレンス使えるとやっぱり色々すっきりする感じありますね・・

テスト

最後のトピック、テスト&ぶつかり稽古ですが、テスト&モジュールはそれぞれそこそこクリアできたのですが、そもそものテーマというか根本をちょっと把握できてなくて、解答編でまこぴーさんとmoznionさんが実演してくださったのを見ると、先にテストを書いて、それに合わせてというかコードを書いていく、という順番だったところ、ぼく&パートナーさんは先にコードを書いて、それを追う感じでテストを書いて確認、みたいな感じにしていたので、その辺ちょっと惜しかったなと。面白みがもう一段階味わえてなかったなと、そういう意味では良い経験だったかなとも思いました。
最後に一応、最終問題の問題と回答を載せておきます。

  • 隣の人とペアを作ってぶつかり稽古(ペアプログラミング)をしましょう. 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が出来る?」と聞きいてみましょう

ということで、まずテストの「yapc.t」。

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use YAPC;

is YAPC::year(), 2014, 'YAPC_year';
is YAPC::month(), 8, 'YAPC_month';
is YAPC::day(), 28, 'YAPC_day';
ok YAPC::is_yet('2014/03/23'), 'YAPC_is_yet_ok';
ok !YAPC::is_yet('2016/04/23'), 'YAPC_is_yet_not_ok';

done_testing();

次に「YAPC.pm」。

#!/usr/bin/env perl
use strict;
use warnings;

package YAPC {
    sub year {
        return '2014';
    }
    sub month {
        return '8';
    }
    sub day {
        return '28';
    }
    sub is_yet {
        my $str = shift;
        if (defined $str) {
            if ($str =~ /(\d{4})\/(\d{2})\/(\d{2})/) {
                if ($1 < 2014) {
                    return 'Yet!';
                } elsif ($1 == 2014 && $2 < 8) {
                    return 'Yet!';
                } elsif ($1 == 2014 && $2 == 8 && $3 <= 28) {
                    return 'Yet!';
                } else {
                    return 0;
                }
            } else {
                return 0;
            }
        } else {
            return undef;
        }
    }
}

1;

はい。これ途中で「defined ~」というif文を入れていますが、初めこれナシでやったら

Use of uninitialized value in pattern match 〜

というエラーが出たのでいろいろ検索してこれを入れた感じです。どうなんだろ・・

ともあれ、まずこのモジュールを「YAPC」ディレクトリ内のlibディレクトリに入れて、テストをtディレクトリに入れて、あらためてYAPCディレクトリから以下のようにテストを実行してみます。

$ prove -Ilib t/yapc.t -v

結果。

t/yapc.t ..
ok 1 - YAPC_year
ok 2 - YAPC_month
ok 3 - YAPC_day
ok 4 - YAPC_is_yet_ok
ok 5 - YAPC_is_yet_not_ok
1..5
ok
All tests successful.
Files=1, Tests=5,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.03 cusr  0.01 csys =  0.06 CPU)
Result: PASS

はい、ええ、まあ何とかなってるかな・・
ほんとはココで、最初にわざとテストをコケさせつつ徐々にコードの方を直していく、という経過を示せると面白いのかもしれないですが、時間もあまりないので淡白にご紹介しました。

ちなみに最終問題の最後にあった、「Time::Piece」と「Test::MockTime」もけっこう調べてみたのですが、これはちょっとわかんなかったですね・・
https://github.com/perl-entrance-org/workshop-2013-05/blob/master/slide.md#%E3%83%AF%E3%83%B3%E3%83%A9%E3%83%B3%E3%82%AF%E4%B8%8A%E3%81%AE%E5%AE%9F%E8%A3%85%E3%81%B8

でも本講の方に参加されていた id:m_shige さんのレポートを拝見したらがっつり対応されていてすごく参考になりました。よろしければぜひどうぞ。
http://m-shige1979.hatenablog.com/entry/2014/02/08/183515

ところで最後のぶつかり稽古というか、ペアプロみたいなの、すごい面白かったですね。長時間はきついかもしれないですが、適切にやれば暗黙知的な知見をうまく交換・交感できるように思いました。

そのときにちょっと相手の方とも話していたのですが、Perl入学式の資料について、講座のときは以下のスライドを見ながらやっていますけど、
http://perl-entrance.org/2013/handout/perlentrance05/index.html

テキストを効率的に読みたい&探したいようなときはこちらのマークダウンファイルの方が見やすいと思うのでお勧めします。(人によるとは思いますが)
https://github.com/perl-entrance-org/workshop-2013-05/blob/master/slide.md

以上です!!