Blog::kfly8

prove t/foo/bar/baz.t

型に厳しい言語とCpanel::JSON::XS::Typeで仲良く

この記事はPerl Advent Calendar 2018の10日目の記事です。

JSONをdecodeする時、例えば、123"123"は型に厳しい言語であれば、別々に扱いたいものです。 ですが、Perlでぼやっとencodeすると、これらをいっしょくたにしてしまいます。

例えば、次のコードは123とencodeされることを期待しても、"123"とencodeします。 これは、123perl内部の値のフラグが意図せず、文字列になり*1、encodeされてしまったからです。

use JSON::XS;

my $row = { id => 123 };
# ぼやっと hash key として触る
my %fg; $fg{$row->{id}} = 1;

print encode_json($row);
# => { "id":"123" }

これでは、型に厳しい言語と仲良くできませんね..! 型指定無くencodeしできるのは手軽ではありますが、decode側の言語が期待する型通りencodeできれば、異なる言語間で仲良くできそうです *2

ここでは、PerlCpanel::JSON::XS::Type を用いて、期待する型を定義し、encodeする方法について紹介します。

Cpanel::JSON::XS::Typeで型指定をしてencodeする例

早速ですが、型指定をしてencodeする例を示します。 次のコードは、{ id => JSON_TYPE_INT }という型指定をし、値を"123"という文字列でなく123という数値にencodeしています。

use Cpanel::JSON::XS;
use Cpanel::JSON::XS::Type;

# 型指定
my $type = { id => JSON_TYPE_INT };
print encode_json({ id => "123" }, $type);
# => { "id":123 }

encode_jsonの第二引数に型を指定しなければ、従来のencodeを行うので取り入れやすいのではないかと思います。

Cpanel::JSON::XS::Typeで型を定義する

JSONで期待される型は、基本型は数値、文字列、真偽値、nullの4種で、また構造を表現するため、配列、ハッシュがあります。 基本型は、それぞれ、JSON_TYPE_INT|JSON_TYPE_FLOAT、JSON_TYPE_STRING, JSON_TYPE_BOOL、JSON_TYPE_NULLという定数で表し、もし基本型がNULLを取りうるのであれば、_OR_NULL というsuffixを付けます。 配列、ハッシュは、[],{}というリテラル表現か、json_type_arrayofjson_type_hashofといった関数を用います。 詳細は、ドキュメントを確認ください。

例えば、このようなデータをencodeする例を考えてみます。

# http://dist.schmorp.de/misc/json/short.json
my $data = {
    'id' => undef,
    'params' => ['user1','we were just talking'],
    'array' => ['1',11.01,234,-5,1e5,1e7,!!1,!!0],
    'method' => 'handleMessage'
};

この場合、型は次のように定義できます。見たままの型定義で、特に理解に支障はないと思います。

my $type = {
    id     => JSON_TYPE_INT_OR_NULL,
    params => json_type_arrayof(JSON_TYPE_STRING),
    array  => json_type_arrayof(JSON_TYPE_INT_OR_NULL),
    method => JSON_TYPE_STRING,
};

この型を指定し、encodeすると次のようになります。

print encode_json($data, $type)
# => {"array":[1,11,234,-5,100000,10000000,1,0],"id":null,"params":["user1","we were just talking"],"method":"handleMessage"}

まとめ

  • decode側が期待する型通りに、Perlがencodeできない時がある
  • そこで、Cpanel::JSON::XS::Typeを用いて期待する型を指定する方法を紹介した

宣伝

YAPC::Tokyo 2019 が1/26(土) に開催されますね! 最高に楽しくなると思う!! ので、来て欲しいです!!!!

チケット販売期限が、今週末予定なので、買い逃しなく!!!!!!

yapcjapan.org

*1:SVが、IV→PVIVに変わった

*2:実際、JSON Schema、OpenAPI、Protcol Buffers、GraphQL といった中間表現を用意し、encode/decodeするコードを利用言語ごとに自動生成するアプローチで、言語間の境界を意識する経験をした人もいるかもしれません。

WEB+DB PRESS vol.107 「Perl Hackers Hub」 第52回「Perlで堅牢な開発」に寄稿しました

本日10月24日(水) に販売されるWEB+DB PRESS vol.107 で Perl Hackers Hub 第52回「Perlで堅牢な開発」を書かせていただきました。

技術評論社の稲尾さん、監修いただいた牧さん、応援してくれたモバファクの同僚、そして家族に協力してもらい、執筆することができました。本当にありがとうございました。

大それたお題になってしまっていますが・・ Perlでの構文チェックや型制約の活用方法などをいくらかまとめることができたのかなと思っています。 少しでもお役に立てれば幸いです。

f:id:kfly8:20181010160652j:plain

WEB+DB PRESS Vol.107

WEB+DB PRESS Vol.107

  • 作者: 大竹智也,浦井誠人,平野朋也,村田紘司,上野学,末永恭正,久保田祐史,吉川竜太,上野博司,牧大輔,西郡卓矢,桑原仁雄,小林謙太,竹馬光太郎,池田拓司,はまちや2,竹原,長谷川智希,北村壮大,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/10/24
  • メディア: 単行本
  • この商品を含むブログ (1件) を見る

宣伝

来年の1/26(土) に YAPC::Tokyo 2019 が開催されますね! 微力ながら運営をやらせてもらっていますが、着々と準備が進んでいて、ワクワクしています!! 楽しみですね!!!

yapcjapan.org

About Dancer2 (ja)

この記事は、Perl5 Advent Calendar 2015 の20日目の記事です。

Web Application Framework の Dancer2 について紹介したいと思います。

まずは、YAPC::Asia 2014 の LT から。 こちらは、Dancer2 のリリースマネージャーの SawyerX による、Dancer2の超速紹介です。 Dancer2 の要点が凝縮されたLTです。


LT / Dancer2 / Sawyer X

目を凝らすとこんなスライドが見えます。DSLで、ルーティングを記述します。

package MyApp;
use Dancer2;

get '/' => sub {
   template index => {
      name => 'Sawyer X',
      event => 'YAPC::Asia'
   }
};

JSON を返却するようなAPIも簡単に用意できます。

package MyApp::API;
use Dancer2;

set serializer => 'JSON';

get '/' => sub {
    return {
        hello => 'world',
    }
};

Dancer2 は Plack化されているので、plackupできます。

# app.psgi
use MyApp;
my $app = MyApp->psgi_app;
plackup app.psgi

ここまでが、LTの抜粋です。

Dancer2 は、元々、Ruby のSinatra を Perl に port した Dancer の次バージョンです。 Dancer の DSL はほぼそのままに、設計面での調整が大幅に入っています*1

例えば、以下のような対応が行われています。

  • Plack 対応
  • DSL 実装を、Top 空間から、Dancer2::Core::DSL に分解
  • Moo 採用
  • デザインパターンの調整(デメテルの法則...)

この対応に関して、2011 年の Dancer Advent Calendar にて、(超訳ですが)「全部書き換えるぞい」という記事があります。

Dancer 2, or Why I Rewrote Everything | PerlDancer Advent Calendar

利用者が多数のプロダクトにおいて、こういった完全な書き換えは、個人的な興味をそそりました。 今現在も活発に開発され、Dancer という名前を冠するPerl カンファレンスも開かれています。

Dancer を作った sukria のポスト。 https://blog.sukria.net/2015/10/22/perl-dancer-2015-reportblog.sukria.net

最近の開発状況の話。


SawyerX - State of Dancer

(Dancer2::XS ってあって、すごいってなった)

もう少し、Dancer2 の特徴を触れて、記事を終わりにしたいと思います。
(と言っても、ほぼ、2014 年の dancer advent calendar *2 からの抜粋です。

fatpack、つまり一枚スクリプトにするのをサポートしています。 (なので、XSモジュールは、recommend 扱いです)

ポータビリティを考えると良い選択になるかもしれません。

コマンドラインで利用も考慮されています。

use Dancer2;

warn config->{environment}; # => development
warn dancer_version;        # => 0.165000

複数のアプリケーションの同居が考慮されています。 Dancer2 に appname を渡すことで、config やdbh などが、appname ごとに管理されます。

以下の例だと、 MyApp::Foo1, Foo2 で、一つの Dancer2::Core::App obj が作られ、 Bar にもう一つの Dancer2::Core::App obj が作られます。

package MyApp::Foo1 {
  use Dancer2 appname => 'foo';
  get '/' => sub { ... }
}

package MyApp::Foo2 {
  use Dancer2 appname => 'foo';
  get '/' => sub { ... }
}

package MyApp::Bar {
  use Dancer2;
  get '/' => sub { ... }
}

まとめ

  • なんだか Dancer2 楽しそうだぞ

明日は、akihiro_0228 さんです。

*1:http://advent.perldancer.org/2014/2

*2:SawyerX の一人アドベントカレンダー。つよい。

設定とFixtureのテスト

この記事は、モバイルファクトリー Advent Calendar 2015 2日目の記事です

昨日は、nekobato さんの superagentとaxiosの使い分け でした  

Perl Advent Calender 2014 の最高の記事の ソースコード以外もとにかくテストする。もしくはカバレッジだけではダメだという話 から、 アプリケーション設定ファイルのテストFixtureのテスト の例を書いてみたいと思います

アプリケーション設定ファイルのテスト

本番環境、開発環境、テスト環境で設定が過不足がないかチェックするため、 Test::Deep, Test::Deep::Matcher を利用しています

テストの流れとしては

  1. 本番環境の設定値を、Test::Deep::Matcher のmatcher に変換
  2. 開発環境、テスト環境の設定値が、matcher にあうか比較

です

例えば、以下のような本番設定があったとして..

# production.pl
{
  servers => [
    'XX.XX.XXX.XXX:11211',
    'XX.XX.XXX.YYY:11211',
  ],
  options => {
    utf8 => 1,
  },
}

再帰的にmatcherに変換してあげます

use Test::Deep::Matcher;
use Test::Deep qw//;

sub convert_matcher {
    my $v = shift;

    if (!ref $v) {
        return Data::Util::is_integer($v) ? is_integer
             : Data::Util::is_number($v)  ? is_number
             : Data::Util::is_string($v)  ? is_string
             : Data::Util::is_value($v)   ? is_value
             : undef;
    }
    elsif(ref $v eq 'ARRAY') {
        return Test::Deep::array_each(convert_matcher($v->[0])) # XXX: 仮定
    }
    elsif(rev $v eq 'HASH') {
        return +{ map { $_ => convert_matcher($v->{$_}) } keys %$v }
    }
}

あとは比較するだけです

my $expect = convert_matcher($production_conf)
cmp_deeply $development_conf, $expect;

(余談)

  • 上の例では、本番環境の設定から(雑な)型推定をしました
  • 設定の型定義を用意しておけるなら、その方が良いかもしれません
    • 異常値などの定義をしやすいですし

以下、crystal の例です

require "json"

class Config
  JSON.mapping({
    servers: String,
    options: { type: ServerOptions },
  })
end

class ServerOptions
  JSON.mapping({
    utf8: Int32
  })
end

Fixture のテスト

リレーションチェックのため、 DBIx::Schema::DSL, SQL::Translator::Schema を利用しています

チェックには、外部キー制約を利用します
実際のアプリケーションでの利用は、諸事情 でしていないですが、 テストでの利用では最高に便利です
(この辺の外部キーありなしのスイッチを、no_fk_output/output だけで簡単にできるのが、DBIx::Schema::DSL めっちゃ便利だーってなって便利です)

流れは、

  • 外部キー制約つきのスキーマを用意して、INSERT する

だけです。 ただ、エラーメッセージを、人間が読みやすい形にするため、地味にループを回すようなコードにしています:p

# Schema
use 5.014002;
package MyProj::DB::Schema {
    use DBIx::Schema::DSL;

    create_table 'module' => columns {
        integer 'id', primary_key, auto_increment;
        varchar 'name';
        integer 'author_id';

        add_index 'author_id_idx' => ['author_id'];

        belongs_to 'author';
    };

    create_table 'author' => columns {
        integer 'id', primary_key, auto_increment;
        varchar 'name', unique;
    };
}
# belongs_to.t

$db->execute(MyProj::DB::Schema->output);

my $schema         = MyProj::DB::Schema->context->schema;
my $module_schema  = $schema->get_table('module');
my $constraints    = $module_schema->fkey_constraints;

for my $fkey (@$constraints) {
    my $ref_table = $fkey->reference_table;

    my @errors = try_load($ref_table);
    ok !@errors
       or note explain @errors;
}

sub try_load {
    my ($table) = @_;

    my @errors;
    for my $row (@{$FIXTURE_DATA{$table}}) {
        try {
            $db->insert($table => $row);
        }
        catch {
            push @errors => shift;
        };
    }
    return @errors;
}

まとめ

  • アプリケーション設定ファイルのテストFixtureのテスト の実装例を紹介しました

明日は、 nekobato さんです!

Hello, Crystal

この記事は Crystal Advent Calendar 2015 の 2 日目の記事です

昨日は、pine613 さんの これから Crystal を始める方へ: Crystal 日本語情報まとめ でした。 公式ドキュメントの日本語訳が整っているの素敵ですね!! http://ja.crystal-lang.org/

今日は、crystal の setup から、heroku に deploy するまで、です。
完全、後発です。厳しいです。詳しいことは、↓を見ると良いのではないでしょうか!?

subvisual.co

zephiransas.github.io

setup

  • *env 最高
% anyenv install crenv
% crenv install 0.9.1
% crenv rehash
% crystal -v
Crystal 0.9.1 [b3b1223] (Fri Oct 30 03:26:53 UTC 2015)

initialize app

% crystal init app app # `app` という名前のapplication の雛形用意
% cd app
% git commit -m 'initial commit'

create app

  • crystal-lang.org の冒頭のHello返すだけのHTTP server に OptionParserでport食わせられるようにしただけ
% cat src/app.cr

require "http/server"
require "option_parser"

port = 8080
OptionParser.parse! do |parser|
  parser.on("-p PORT", "--port=PORT", "Set server port") { |p| port = p.to_i }
end

server = HTTP::Server.new("0.0.0.0", port) do |req|
  HTTP::Response.ok "text/plain", "Hello"
end

puts "Listening on http://0.0.0.0:#{port}"
server.listen
  • 試しに実行してみる
# 即時実行
% crystal run src/app.cr
Listening on http://0.0.0.0:8080

# ビルドしてあげる
% crystal build src/app.cr
% ./app -p 5000
Listening on http://0.0.0.0:5000

deploy to heroku

  • Procfile とりあえず用意して、手元の環境で、ps 動かしてみる
% cat Procfile
web: ./app -p $PORT
% heroku local
forego | starting web.1 on port 5000
web.1  | Listening on http://0.0.0.0:5000
% git add .
% git commit -m 'update'

heroku create

  • ↓のカスタムビルドは、適宜・・好きなのを用意してください
    • 今回は手抜きで、crystal version が、手元では、0.9.1 だけど、0.9.0 でビルドされるカスタムビルドパックを利用しました:p
    • 今回のカスタムビルドの要件は、0.9.0 でのビルド。shard.yml があること。./src/app.cr がソースファイルで、 ./app にリリースビルドされて、Procfile./app を呼び出している感じですね。
% heroku create --buildpack https://github.com/scaint/heroku-buildpack-crystal.git
% git push heroku master

Counting objects: 16, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (16/16), 306.50 KiB | 0 bytes/s, done.
Total 16 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Fetching set buildpack https://github.com/scaint/heroku-buildpack-crystal.git... done
remote: -----> Crystal app detected
remote: -----> Installing Crystal 0.9.0
remote: -----> Installing Shards 0.5.3
remote: -----> Installing Dependencies
remote: -----> Compiling src/app.cr
remote:
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing... done, 409K
remote: -----> Launching... done, v3
remote:        https://arcane-harbor-8307.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/arcane-harbor-8307.git
 * [new branch]      master -> master
% heroku open

反省

アドベントカレンダー事前に準備する

明日は、 5t111111 さんです!

DBIx::Schema::DSL::Dumper というのを書きました

kfly8/DBIx-Schema-DSL-Dumper · GitHub

songmuさんの書いたDBIx::Schema::DSL を使いたい状況が出て来て、
すでに結構もりもり書かれてたDDLを移行する為に書いてみました。

使い方は、Teng::Schema::Dumper と同じように、$dbh を渡すだけで、DSLが吐き出せます。

雰囲気は次のような感じです!

use DBI;
use DBIx::Schema::DSL::Dumper;

my $dbh = DBI->connect('dbi:mysql:dbname=test', 'root', '');
print DBIx::Schema::DSL::Dumper->dump(
    dbh => $dbh,
    pkg => 'Foo::DSL',
);

よかったら使ってほしいです!

間接オブジェクト記法のアンチパターン

この記事は Perl Advent Calendar 2014 の 17日目 の記事です。

16日目の記事は magnolia_k_ さんの perlの関数を第一級オブジェクトとして扱う話 でした

目次

はじめに

15日目の記事の id:akihiro0228 さんの記事を読んで、

自分がよくハマってしまう間接オブジェクト記法について紹介したいと思います。
便乗です!

間接オブジェクト記法ってそもそも何?

メソッドを呼び出しの別表現です

$obj->method($a, $b); # 普段の呼び出し

method $obj $a, $b;   # 間接オブジェクト記法

一番なじみやすい例はnewだと思います

my $dog = new Dog;

普段は、誤読につながりやすいのであまり使うことをおすすめしないです
ですが、意図せず間接オブジェクト記法な呼び出しにしてしまったり、 この表現の方が簡潔に書ける例もあったりするので紹介してみたいと思います

間接オブジェクト記法が活きる例

定数を扱う時に便利なconstantプラグマを使ってみます
普段使う時は、以下のような感じで使うと思います

use constant FOO => 'foo';

これを動的に定数定義してみたいとします

動的に定義したい場合は use XXX は、BEGIN { require XXX; do { XXX->import } } のsugarなので、
このsugarをほどいて1つ1つ自分で順に追ってやれば変数だったりが使えます

package XXX;

BEGIN {
    require constant;
    constant->import(FOO => 'foo' x 4);
}

say FOO; # => foofoofoofoo

間接オブジェクト記法を使ってみると以下のようになります

package YYY;

BEGIN {
    require constant;
    import constant FOO => 'foo' x 4;
}

say FOO; # => foofoofoofoo

どうですかね?
普段のconstantプラグマの使い方に近く、簡潔に書けているように思います


逆に今度は、よくあるハマる例を書いてみます

ハマる例1 Try::Tiny のuse 漏れ

ずばり、 id:karupanerura 先生の話を例を取ってみます。

use strict;
use warnings;
use utf8;
use feature 'say';
 
# XXX わざとuseしないで、errorを起こす
#use Try::Tiny;
 
try {
    say 'foo';
    say 'bar';
}
# =>
# foo
# bar
# Can't locate object method "try" via package "1" (perhaps you forgot to load "1"?) at try2.pl line 6.

このエラーだとよくわからないので、 7日目の id:papix さんと11日目の id:hisaichi5518 さんが紹介しているB::Deparseを使います

# perl -MO=Deparse %

use utf8;
use warnings;
use strict;
use feature 'say';
do {
    say 'foo';
    say 'bar'
}->try;

do の最後に評価された 1 を package として、1->try みたいなことをしています

# Can't locate object method "try" via package "1" (perhaps you forgot to load "1"?) at try2.pl line 6.

packageが1だとちょっと分かりにくいと思うので、
次の例を見てみてください

use strict;
use warnings;
use utf8;
use feature 'say';
 
#use Try::Tiny;
 
try {
    say 'foo';
    bless {}, 'foo'; # XXX obj を最後においてやる
}
# =>
# foo
# Can't locate object method "try" via package "foo" at try2.pl line 6.

Deparse すると以下のようになります

use utf8;
use warnings;
use strict;
use feature 'say';
do {
    say 'foo';
    bless {}, 'foo'
}->try;

今度のエラーと、Deparse した例を見ると、foo->try と呼び出しされたことが分かってもらえると思います

# Can't locate object method "try" via package "foo" at try2.pl line 6.

最後にちゃんと use Try::Tiny した例です

use strict;
use warnings;
use utf8;
use feature 'say';

use Try::Tiny;

try {
    say 'foo';
    bless {}, 'foo';
}
# => foo
# perl -MO=Deparse %

use utf8;
use Try::Tiny;
use warnings;
use strict;
use feature 'say';
try sub {
    say 'foo';
    bless {}, 'foo';
}
;

Deparse したコードを見ると、try に coderef が渡されていることが分かると思います

余談 code block

Try::Tiny の try のコード を見ると、次のようにサブルーチン名に(&;@)といった指定がされています

sub try (&;@) {

これはプロトタイプと呼ばれるものです

http://perldoc.jp/docs/perl/5.6.1/perlsub.pod#Prototypes

これにより、blockで、coderef を表現できるので、より簡潔な記法ができます
try 以外にも、grep, mapなどが身近な例だと思います

coderef を実行しながら、sumを取る関数を実装してみます

まずは、prototypeを使わない例

use strict;
use warnings;
use utf8;
use Test::More;
use Test::Exception;
use Test::Perl::Critic;
use List::Util qw/sum/;

sub sum_map {
    my $cb = shift;
    sum map { $cb->($_); } @_;
}

subtest 'basic' => sub {

    is sum_map(sub { $_[0] * 10 }, 1..10), 550;
};

subtest 'dies' => sub {

    dies_ok { sum_map { $_ * 10 }, 1..10; }

    # =>
    # Use of uninitialized value $_ in multiplication (*) at proto.pl line 23.
    # Odd number of elements in anonymous hash at proto.pl line 23.
    # Not a CODE reference at proto.pl line 10.
};

subtest 'critic' => sub {
    critic_ok($0);
};

done_testing;

次にprototypeを指定してみます

use warnings;
use utf8;
use Test::More;
use Test::Exception;
use Test::Perl::Critic;
use List::Util qw/sum/;

sub sum_map (&@) {
    my $cb = shift;
    sum map { $cb->($_) } @_;
}

subtest 'basic' => sub {

    my $ret = sum_map { $_ * 10 } 1..10;
    is $ret, 550;
};

subtest 'other use case' => sub {

    my $ret = sum_map(sub { $_[0] * 10 }, 1..10);
    is $ret, 550;
};

subtest 'critic' => sub {
    critic_ok($0); ## failed!!

    # not ok 1 - Test::Perl::Critic for "proto2.pl"
    # Failed test 'Test::Perl::Critic for "proto2.pl"'
    #   at proto2.pl line 27.
    #
    # Perl::Critic found these violations in "proto2.pl":
    # Subroutine prototypes used at line 9, column 1.  See page 194 of PBP.  (Severity: 5)
};

done_testing;

critic のテストが通りませんでした
blockが coderef になっているのは、確かに副作用が強いです
その為、多用はしたくないです

以下のように ## no critic と注釈をつけてあげて、
副作用を許容してあげることができます

http://search.cpan.org/~thaljef/Perl-Critic-1.123/lib/Perl/Critic.pm#BENDING_THE_RULES

use strict;
use warnings;
use utf8;
use Test::More;
use Test::Exception;
use Test::Perl::Critic;
use List::Util qw/sum/;

sub sum_map (&@) { ## no critic
    my $cb = shift;
    sum map { $cb->($_) } @_;
}

subtest 'critic' => sub {
    critic_ok($0), '注釈をつけることで回避';
};

done_testing;

ハマる例2? UNIVERSAL

間接オブジェクトわざとらしく使ってみたいと思います canを使っているのがわざとらしいです!

use strict;
use warnings;
use utf8;
use feature qw/say/;

my $dog = bless {}, 'Dog';

if (can $dog 'bow') {
    say 'can bow';
}
else {
    say 'cannot bow';
} # => cannot bow
#perl -MO=Deparse %

use utf8;
use warnings;
use strict;
use feature 'say';
my $dog = bless({}, 'Dog');
if ($dog->can('bow')) {
    say 'can bow';
}
else {
    say 'cannot bow';
}
# original
if (can $dog 'bow')

# Deparse
if ($dog->can('bow')) {

$dogにcan methodは生やしていないですが、
Perlの場合、全てのクラスは暗黙的にUNIVERSAL を元にするようになっているので、
$dog->can で UNIVERSAL->can が呼び出しされます

試しに、UNIVERSALのcanが呼び出しされないように、
このパッケージに can というサブルーチンを生やしてみます

use strict;
use warnings;
use utf8;
use feature qw/say/;

my $dog = bless {}, 'Dog';

sub can { 1 }

if (can $dog 'bow') { # XXX => syntax error
    say 'can bow';
}
else {
    say 'cannot bow';
}

=pod

syntax error at can2.pl line 10, near "$dog 'bow'"
can2.pl had compilation errors.
use utf8;
use warnings;
use strict;
use feature 'say';
my $dog = bless({}, 'Dog');
sub can {
    use warnings;
    use strict;
    use feature 'say';
    1;
}

$dog と 'bow' の間にカンマがなくて、エラーになりました
もちろんカンマを入れれば、次のように、'can bow'と表示されます

このカンマのあるなしの意思表示で、間接記法であるかどうか示します
それは意思表示としては、わかりずらいので、誤読の元になりやすいです

use strict;
use warnings;
use utf8;
use feature qw/say/;

my $dog = bless {}, 'Dog';

sub can { 1 }

if (can $dog, 'bow') { # リストにするため、カンマを入れた
    say 'can bow';
}
else {
    say 'cannot bow';
} # => can bow

このように暗黙に定義されたメソッドで、間接オブジェクト記法が出てくるなんて、
あまりないと思いますが、良い機会なので、
UNIVERSALに生えているメソッドを洗ってみます

Class::Inspector を使うと、can, isa, VERSION, DOES ということが分かります
http://perldoc.perl.org/UNIVERSAL.html の通りです
これで、もう多分、if (isa $foo 'FOO') とやってハマること?もないでしょう?

use strict;
use warnings;
use utf8;

package Dog;

sub new { bless {}, shift };

package main;

use Test::More;

my $dog = Dog->new;

subtest 'isa' => sub {
    isa_ok $dog, 'Dog';
    isa_ok $dog, 'UNIVERSAL';
};

subtest 'methods' => sub {

    ok $dog->can('can');
    ok $dog->can('isa');
    ok $dog->can('VERSION');
    ok $dog->can('DOES');
};

subtest 'methods count' => sub {

    require Class::Inspector;
    is scalar @{Class::Inspector->methods('UNIVERSAL')}, 4;

    is_deeply(Class::Inspector->methods('Dog'), [qw/new/]);
};

done_testing;

ハマる例3 テストにて

生々しいですが、例2のClass::Inspector を使っていて、今、ミスった例です。

use strict;
use warnings;
use utf8;

package Dog;

sub new { bless {}, shift };

package main;
use Test::More;
use Class::Inspector;

is_deeply Class::Inspector->methods('Dog'), [qw/new/];
# XXX failed!!
# => Can't locate object method "is_deeply" via package "Class::Inspector" at can5.pl line 12

=pod
use utf8;
package Dog;
sub new {
    use warnings;
    use strict;
    bless {}, shift();
}
package main;
sub BEGIN {
    use warnings;
    use strict;
    require Test::More;
    do {
        'Test::More'->import
    };
}
use Class::Inspector;
use warnings;
use strict;
'Class::Inspector'->is_deeply->methods('Dog'), ['new'];

これは、要点だけを絞り出すと次のようになっています

package Foo;
sub new { bless {}, shift() }

use strict;
use warnings;
use utf8;

use Test::More;

isa_ok Foo->new, 'Foo';

done_testing;
# Deparse

package Foo;
sub new {
    bless {}, shift();
}
use utf8;
use Test::More;
use warnings;
use strict;
'Foo'->isa_ok->new, '???';
done_testing();
/Users/kfly8/aa.pl syntax OK

実際にはまった例では、

is_deeply Class::Inspector->methods('Dog'), [qw/new/];

が、次のように評価されてしまい、残念なことになりました

'Class::Inspector'->is_deeply->methods('Dog'), ['new'];

括弧付きで呼び出してあげてください

is_deeply(Class::Inspector->methods('Dog'), ['new']);

まとめ

  • Perl には、method を呼び出す方法に、method $obj LIST のような呼び出し方もあり、それを間接オブジェクト記法という
  • 使いどころを(すごい)選べば、import constant FOO => 'foo' のような簡潔な表現になる
  • よくあるハマり方
    • 関数のexport漏れ
      • 余談 prototype
    • 暗黙なmethod定義
    • func Class->foo の Class->func->foo という誤読しやすい評価順

補足 この記事のコードは、gistでも見れますー。

Perl Advent Calender 2014 . 17th · GitHub

明日

18日目は、wanji さんです!

#Perl入学式 のお手伝いさせてもらってきた

Perl入学式 に参加するのは初めてだったのですが、 サポーターとして参加させてもらいました。 お疲れ様でした!特に講師のid:xtetsuji さんお疲れ様でした!!

「サブルーチンと正規表現」がお題でした。 資料はコチラ

寿司はコチラ。

美味かった。

面白かった(?)罠がコチラ

use strict;
use warnings;
use utf8;

sub dump_hash {
    my $hash = @_;
    $hash->{foo};
}

dump_hash({foo => 1, bar => 2});
# => Can't use string ("1") as a HASH ref while "strict refs" in use at foo.pl line 7.

生徒:「1って何?!?!」
おれ:「配列をスカラーで評価して、配列長..ですね...」
生徒:「??????」
おれ:「ですよね!!!!!!!!」

幸い?他のプログラミング言語を触っている人だったからか、 「Perlだと、どう値を使いたいか意思表示できて、 例えば、配列が欲しいのか、スカラが欲しいのかなど。 逆に言うと意思表示に慣れるまで、ハマります!!」 でコンテキストのことを濁し、次のような引数の取り方の例を見てもらったら納得してもらえたよう。

sub dump_hash2 {
    my $h   = @_; # bad...
    my ($h) = @_; # ok (補足: 括弧が、優先順位の括弧に見えてしまったよう。 my ($a,$b) = @_ の例も併わせて紹介して、リストの括弧と分かってもらえた。
    my ($h,)= @_; # ok (さらに補足: 人によっては配列をより意識する為にこう書く人もいる
    my $h   = shift; # ok
    my $h   = $_[0]; # ok
}

蛇足にこんな例も話したら、楽しんでもらえたよう(多分)

my @a  = ('a', 'b', 'c', 'd');
#my @a = qw/ a b c d /; # これで上と同じ意味

my %hash  = ( a => 1, b => 2 );
#my %hash = ( 'a', 1, 'b', 2 );  # これで上と同じ意味
#my %hash = qw/ a 1 b 2 /;  # これも同じ意味
my @b     = ( 'a', 1, 'b', 2 );  # さらに配列として受け止めることもできる

慣れるのは苦労するかもしれないけど、、、
例えば、「対になっているデータが欲しいから、ハッシュにしよう!」
とか思った時に、特にこねくり回したことしなくて良いのが、個人的に楽だと思っている。

完全なる蛇足

楽しみついでに、サブルーチンの定義に関して、 「!」「数字」や「日本語」をサブルーチン名に使ったりする応用例?を書いた。

この辺、YAPC::Asia 2014 のid:karupanerurameta programmingの話 が詳しい。(demoが多いので、動画がおすすめ)

そんなこんなで、Perl入学式楽しませてもらいました!! ありがとうございました! またの機会もよろしくお願いします!

#chibapm 5で好き放題ランダウ記号の話をしてきた

数学の話を息抜きにしてきた。 グラフが、写真なあたりまさに息抜き←手抜き

インデックス構造によって計算オーダーが変わったりするので、 云々かんぬんで久しぶりに数学に触れられて、楽しかった。

chibapmの懐の深さに感謝!

isuconの予選で失格してきた

牡蠣途中

運営の方々、楽しいイベントをありがとうございました!!
kuraチームのid:karupanerura, id:masasuz ありがとうございました!

isucon中にやったこと

サマリーテーブルを使って、ログインの成否を判定するようにした

https://github.com/karupanerura/isucon2014-elimination/commit/b81dcd38b7201c838ab49501a0a91984b13361b4 https://github.com/karupanerura/isucon2014-elimination/commit/2daf40a936fc541841cc50fcb2c408fee7530444

サマリーテーブルを使って、集計を出すようにした

https://github.com/karupanerura/isucon2014-elimination/commit/f4bd9e12c1d17ef1dab601b745c82daed134e504

  • 紆余曲折があり、本戦中にちゃんとやり切れなかった。

  • 競技終了直前に気づいた不具合

https://github.com/karupanerura/isucon2014-elimination/commit/f4bd9e12c1d17ef1dab601b745c82daed134e504#diff-4f430011a6a953bf6b408bc9906c5478L171

push @user_ids => $row->{login};


$row->{login}...


_人人人人人人人人人_
> idじゃない!! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y ̄

TODO

userデータをオンメモリーにした

https://github.com/karupanerura/isucon2014-elimination/commit/2ee1d266ea6f076556e578f17ed6bf4c6b8c60b1

TODO

slack

https://github.com/karupanerura/isucon2014-elimination/issues/5

捗った

TODO

slides.comを使ってみた

App::revealupで作成したslideを公開する時、 今までgithub.ioを使っていたのですが、 reveal.jsで紹介されているslides.comを使ってみました。

Slides – Create and share presentations online

結論

markdownを作ってスライド作るワークフローはほとんど(後述)変えずに
簡単にスライドが用意できて、さらに以下のようなメリットがあったので、
個人的にはもっとslides.com 使いたいなーと思いました!

(スライドの内容はともかく)試しに作成した結果

元々はこんな感じ。-> slide / github

メリット

ヘルプ一覧にはもっと他の機能についての紹介が
簡潔に書いてあって、わかりやすい!
Knowledge Base – Customer Feedback for Slides

やること

markdownで作ったスライドを、slides.comにあげる為にやることは、
大きく一つだけ。

markdownから適当なHTMLファイルを用意してimportするだけ。
importについてはコチラを参照

importするHTMLの用意

  • App::revealupで立ち上げたスライド(0.0.0.0:5000を想定)を、Chrome経由で保存(Cmd+S)*1
  • 画像のパスを絶対パスに変えてあげる
  • data-markdown="" 属性を消す

雑だけど、つまりこんな感じ

Cmd+S
vim 0.0.0.0.html
:%s;./0.0.0.0_files;https://dl.dropboxusercontent.com/u/XXX/img/YYY;gc
:%s;data-markdown="";;g
:cat % | pbcopy
# -> slides.comの編集画面からimport

これを自動化できればもっと楽そう。*2

まとめ

slides.comを使えば、markdownでスライド作るワークフローは殆ど変えずに、
プレゼン面、ソーシャルメディアの面で多くのメリットが得られて、幸せ!

*1:外部ファイルのmarkdownをhtml tagに展開した物が欲しいのでこんなことやってる。展開済みのhtmlを用意できればなんでもいい。展開するときに、画像パスとdata-markdown属性だけ気をつける

*2:slides.comがmarkdownのimportに対応すればもっと楽ではある、、

#gotandapm たのしんできた

Gotanda.pm #2
飲んだり発表したり話を聞いたり飲んだりしてきました1
主催のid:karupaneruraありがとうございました!!!!!

isucon楽しい!!!!ので参加すると良いって気持ちで発表してきました><

gotandapm スライドとmarkdown

コード多めにしてしまったので見づらかった方すいません><

gotandapmはまた12月の開催されるそうで楽しみです!!!

ほんとにあったスキーマの話「ソーシャルゲーム」と題して、YAPCのトーク応募してみた

YAPC::Asiaのトークの応募数が尋常じゃないので、駆け込みブログです><
(YAPC終わった後に、yet another yapcをするしかないんじゃないか?!)

http://yapcasia.org/2014/talk/show/ce6e777e-fb91-11e3-b7e8-e4a96aeab6a4

アプリケーション層でも、コードの規模感が少し大きくなった、
「続・初めてのPerl」くらいなレベル感な話になれば、、いいな、、と思っています。

以下はなんとなく話せればと思う事の断片。

Hello Android

いまさら、、あんどろいどにはろーわーるどしました(え

備忘録です。 MyFirstAppを作成するところまでは手順通りだった。それだけなのにつかれる事態になってしまった><

ant debugでまずはまった

まず、ant がないと怒られる。次で解決

brew update
brew install ant

build.xmlがなくて、buildできない

android update project --name MyFirstApp --target android-19 --path ~/android/workspace/MyFirstApp

apkを端末にインストールできなくてはまった

error: device offline

  • adb devicesしてもでてこない。端末外して、次をしてから、接続し直して解決。
adb kill-server
adb start-server

error: more than one device and emulator

  • 端末を指定して解決
% adb devices
List of devices attached
emulator-5554   offline
XXX      device
% adb -s XXX  install bin/MyFirstApp-debug.apk

とりあえず、はろーあんどろいど

isucon に負けて悔しいので、問題を速く解決するためのパターンを考えてみた

isucon決勝からもうだいぶ日が経ってしまいました、、その間、負けて、id:karupanerura先生に、げきおこされる夢も見ました。

振り返ります。

isuconでスコアを残しているチームは技術力もさることながら、 問題解決能力が高いと思うのです。もの凄く。

近づくにはどうすればいいんだろうと、頭の整理のために、 自分が普段感じている問題解決のパターンを書いてみました。 特にisuconの反省なので、与えられた問題を良い感じに速くするための解決方法寄りだと思います。

パターン


  • 処理*1しない
  • 処理したらメモしておく
  • 処理の下準備をする
  • 処理をまとめる
  • 処理を少なくする

  • 処理を外にお願いする
  • 必要な分だけ、処理する
  • 処理を得意な人にお願いする
  • 処理関係者を減らす

  • 処理するヒトを増やす
  • 処理できるヒトを良くする

  • 仕様を仕様を削る

「isucon知識足りなくて、負けたわー、じゃー、ほげほげ勉強しよう!」っていうんじゃ、 思考停止だと思っています。

また知らない技術領域が出て来ても、 どうにかこうにか対応できるようになりたいなーと思った感じです (知識不足は、ちゃんと補完しなきゃですが、、)

箇条書きで終わりもアレなので、 良い例かは分からないですが、パッと思いつく例を書いてみます。

処理しない

一番、大事なことだと思います。処理しないで、済むなら、処理しない。 やらないですむなら、やらない。

1 + 1

たたきこみ

処理したらメモしておく

今回のisucon決勝なら、画像投稿時、ないし、初回呼び出し時などで、キャッシュをするなど。

どこでキャッシュするか、いつキャッシュするか、メモしたものをどう更新するかは色々ですが、やったことは、またやりたくないというのは(領域が許せば)良い。

処理の下準備をする

indexや、辞書作りなどの学習

処理をまとめる

都度の処理呼び出しコストを減らす。 描画処理を減らすために、DOMに書き出さないなど。

処理を少なくする

処理を無くせはしないけど、少なくすることはできるっていうのはある。 処理内容が減れば、大小あれど、一応嬉しい。

# あるある
$dbh->query() for 1..100

ここから、ちょっと、トレードオフが大きくなりそうな、パターン。

処理を外にお願いする

処理が完了するまで、次の処理が出来ないのはもったいない なので、他の人にお願いしたくなったりすることもある

必要な分だけ処理する

重い処理の割には、意外と使われなくて、事前準備してなくて構わないというのはあるなと。 そしたら、必要になったときに、処理すればよい。

処理を得意な人にお願いする

言語レベルか、機械レベルか、ソフトウェアか、、など。 これで良さそうな判断ができるかどうかは知識量に凄い左右されると思っている。

それでも得意な人にお願いしたいというのは変わらないはず。 使いこなせる武器が多いのは、視点が高くなる。

処理関係者を減らす

全てを一番ベストな人にお願いしても、1000箇所で処理が行われるとしたら、 その処理の受け渡しのコストが目につくかもしれない。

処理するヒトを増やす

  • 1人でやるより、2人で処理出来る方が捗ることもある。
  • スケールアウト

処理できるヒトを良くする

  • スケールアップ

仕様を削る

  • その仕様、、、、その仕様、、い、、るかも、、いや、いら、らないかも、、 っていうのは、バッサバッサ切れればいいね。
  • 難しい処理の方がお金になったりすることもあるから、難しいね。でも無駄なのもあるよね。
  • エンジニアから、一歩踏み出すのも、問題解決する為の一つの手段。

最後に

id:karupanerura先生、id:masasuz先生、牡蠣鍋打ち上げしましょう> <

KAYACさん、LINEさん、isuconありがとうございました!!!!!

*1:仕事に置き換えても面白いかも?