WEB+DB PRESS Vol.109 に寄稿させていただきました

WEB+DB PRESS Vol.109

紹介

WEB+DB PRESSVol.109(技術評論社:gihyoより2月23日発売予定)のPerl Hackers HubコーナーにサーバレスでもPerlというテーマで寄稿させていただきました。

ぜひお買い求めください!

Perl Hackers Hub – Perl Hacker達による旬の技術の紹介

Perl Hackers HubとはWEB+DB PRESS誌内の連載のひとつであり、Perl Hacker自身が筆を執り旬の技術を紹介するというものです。

gihyoさんのPerl Hackers Hubのページには、Perl Hackers Hubについて以下のような説明がついています。

本連載は,第一線のPerlハッカーが回替わりで,Perlの旬な技術について解説していきます。

記事概要

Microsoft Azure Functionsを使ってPerlスクリプトを実行します。スクリプトの内容は、「画像URLを与えると、画像の顔認識結果(年齢と性別)をJSON形式で返すWeb API」です。

サンプルコードとして、Azure上に環境とプログラムを全自動でデプロイしてくれるスクリプトを収録してあります。Azureアカウントがあれば、最初の無料枠で試せる範囲の内容となりますので、ぜひともお試しください。

謝辞

この度機会を与えてくださった@songmuさん、Perl Hackers Hubコアメンバーの皆さん、そして一緒に原稿の仕上げをしていただいた@inaoさん、本当にありがとうございました。

特に@inaoさんには度々ご迷惑を掛けてしまったにもかかわらず、辛抱強く対応してくださいました。感謝しきりです。

また、Azureについて深く言及した内容の記事はWEB+DB PRESS誌上では初めてだそうです(本当ですか?)。私も驚きました。正直な話、流石にAzureについては過去に誰か書いているだろうと思っていたのですが…意外なところで「誌上初」だったんですね。

ともかく私としては、今回の記事がPerl Hackers Hubというコーナーを通じて、様々な技術に触れるきっかけを読者の皆様に作ることができたらいいな、と思っています。

YAPC::tokyo 2019で登壇しました

YAPC::Japan 2019にて「実演サーバレスPerl – 顔認識データを扱おう」というタイトルで登壇させていただきました。見に来てくれた方、本当にありがとうございます。

そして、結局ライブコーディングするための時間が足りず、見せ場を作れなかった点、本当にごめんなさい。あの後、見に来てくれていた同僚や@note103さんに励まされたのが本当にありがたかったです。

資料

敗因

20分で、受け手側に前知識が必要な内容をライブコーディングするのは、あまりにも無謀でした。

前知識のインストールが終わった時点で7分ほど経過しておりました。さらにクラウド側(Azure)の処理待ち時間が思った以上にかかる。

これではまずいということで、事前に用意しておいた出来上がり環境を使って説明していきましたが、それでも時間が足りず、コードを書くところをお見せできませんでした(コードそのものをお見せすることはできたのですが・・・)。

補足(というか宣伝)

今回のセッションをおさらいする内容で、Web+DB Press誌 2019年2月号のPerl Hackers Hubに寄稿させていただいております。まだ発売まで時間がありますが、より詳しく解説されているものとなりますので、興味のある方はぜひお買い求めください!

また、Web+DB Press誌ではサンプルコードも提供します。サンプルコードは、今回の顔認識システムを自動でAzure上に構築するスクリプトとなっておりますので、構築する手間が惜しいが試すだけ試してみたい、という方にもおすすめです。

前夜祭でもLTしました

本編のみ参加の方はご存じないかもしれませんが、前夜祭でもLTで発表しました。Webappにおける@INC Hooks絶対許せないマンとしてネタに振り切った内容です。

LT資料

Acme::AtIncPoliceのその後

実は昨日Acme::AtIncPoliceissueが立ちまして。なんだろうと思ってみてみると、「Testで使ってるTest::Exceptionが入ってないからTest失敗するんですが」という内容でした。

それを受け、今朝慌ててTest::Exceptionをdependencyとして登録し、shipit。なんと0.01をshipitしてから、たった2日で0.02をshipitすることとなってしまいました・・・

感想など

@songmuさんベストトークおめでとうございます!感極まって感情が溢れ出たときに、周りで何名かもらい泣きした人がいたんですが、songmuさんの情熱があるからこそなんだろうなと。

個人的には@risouさんのPerl6トークと@hitode909さんのWebVRトークがかなり良い話だと思いました。hitode909さんからは豆本の夢日記を頒布していただきました。家宝にします。

Azure Functions v1 + Node.jsなアプリケーションをAzure Functions v2に移行してみる話

このエントリはServerless2 Advent Calendar 2018の17日目のエントリとなります。

半月ほど前業務で、稼働中のAzure Functions v1 + Node.jsなアプリケーションをAzure Functions v2に移行する作業を行いました。

その際、今後も似たような移行作業があり得そうだと考え、この作業をざっくりこなしてくれるスクリプトを作りました。それがytnobody/azure-func-migrate-nodeです。ちなみにPerl製ではなくNode.js製です。

如何せんやっつけで作ったスクリプトですので、漏れ・抜けなどはあるかもしれませんが、お手すきの際にでもpull-reqしてくださると幸甚です。

移行のために最低限必要な処理

大きく分けて、以下の作業が必要となります

  • bindingsに対応したextensionsをインストールする
  • ランタイムバージョンを2.0に設定する

bindingsに対応したextensionsをインストールする

実際のextensionsインストール作業はfunc extensions installに委譲しているのですが、これまでv1で使っていたbindingsに相対するv2 extensionsを検出するロジックは作成しました。

なお、v1では"type": "documntDB"と指定していた箇所は"type": "cosmosDB"に変更する必要がありますので、その対応も行っています。

ランタイムバージョンを2.0に設定する

これもロジックを作りました。

まとめ

ざっくりと移行スクリプトを作りましたので、公開しました。が、だいぶやっつけで作ってありますので、適宜pull-reqをしてくださると幸甚です。

昔書いたGithub止まりのモジュールをほじくり返す – Test::Proc 篇

※このエントリはPerlアドベントカレンダー2018の5日目のエントリとなります。

CPAN(MetaCPAN)でモジュールを公開するということ

Perlでプログラムを書くことに慣れてくると、CPANモジュールを使うようになったり、もっと慣れてくると自分でPerlモジュールを作るようになり、最終的にはCPAN(今はMetaCPANですね)で公開するようになるかもしれません。私は過去にそのような道を歩んできました。

では私は過去にどんなモジュールを書いたのか。ひとまずMetaCPANに上がっているものを一部挙げると、 Otogiri(これはほとんど@tsucchiさんの力です), XML::Diver, Net::Azure::CognitiveService::Faceなどがあります。

PerlモジュールをMetaCPANで公開する前に

しかしながら、MetaCPANでモジュールを公開する前に気にかけておいて欲しいこととして、個人的には以下のような項目を挙げたいと思います。

  • そのモジュールを公開することで不快感を感じる人々が生じないか
  • そのモジュールはセキュリティ的に致命的な問題を抱えていないか
  • そのモジュールは新しい機能を提供してくれるか
  • そのモジュールはプログラミング体験をより幸せにしてくれるか
  • そのモジュールは従来の類似モジュールよりもパフォーマンスに優れるか
  • そのモジュールは従来の類似モジュールよりも依存性が削減されているか

もし上記について該当するかわからない場合は、ひとまずgithubに上げておきつつ、ブログやTwitter等で言及してみましょう。運が良ければ誰かが注目してくれて、レビューをしてくれるかもしれません。これらのことは、Perlモジュールの公開に及び腰になってしまわない程度に、気をつけるとよいのではないでしょうか。

CPANの歴史を少しだけ垣間見る

では、なぜこのように自律する必要があるのでしょうか。それを知るためには過去に目を向けることが重要です。

歴史を振り返ると、charsbarさんによるエントリでCPANは幼稚園児の砂場じゃないよねという、2008年当時、大変考えさせられたものがあります。若い方の中にはこのような歴史を知らない方もいると思いますが、是非とも同じ轍を踏まぬための知識として、知っておいていただければ幸いです。

また、まかまかさんによるPerl同人誌「Acme大全」には、「Acme大全2012」から毎回、巻末付録として「とあるAcmeモジュールの削除について」という章が設けられています(ブログにも同様のエントリがある)。

このように、MetaCPANへモジュールを公開する上では、ある程度その性質を律せられる側面があることは間違いないでしょうし、誰かがつらい思いをしないためにも必要なことであると私は思います。

それでも日の目を見ないモジュールもある

そんな事もあって、実際にはたくさんのPerlモジュールを作っていたとしても、実際にMetaCPANにて公開されるものはその一部でしかありません。ほかにもMetaCPANに公開するのが怖いとか、作者がそこまでの有用性を感じていないだとか、謙遜している、などの理由でMetaCPANで公開されていないPerlモジュールがGithubにはあります。

このようにあえてGithubでの公開にとどめられているPerlモジュールを、私は親しみを込めてGithub止まりモジュールと呼んでいます。

そして今回紹介するGithub止まりモジュールは、拙作のTest::Procです。

Test::Proc – プロセスを起動し、その起動状態をテストする

Test::Procは5年前にGithubにpushされています。今となっては当時の記憶を頼りに解説する他ないという点がいささか不安です。

さて、Test::Procが目指したところは、テストスクリプトでプロセスの起動状態をテストしたいというものでした。

使い方はSYNOPSISにある通りです。(done_testingが抜けていたので、補足してあります。)

use Test::More;
use Test::Proc;


my $proc = test_proc 'my_task' => sub {
    print 'test';
    warn 'dummy';
    sleep 20;
};


$proc->is_work;


$proc->stdout_like( qr/\Atest/ );
$proc->stderr_like( qr/\Adummy/ );


$proc->exit_ok;
done_testing;

あら捜し

レポジトリを見てみると、テストの少なさが気になります。github止まりモジュールにはよくある話かもしれないですが、だいぶ控えめなテストの数だなと感じますね。

そしてインターフェースが片手落ちです。というのも、このTest::Procはtest_procというDSLを提供しており、Test::Proc::Objectインスタンスを返しているのですが、test_proc側にはクロージャ以外でプログラムを起動する方法が提供されていません。Test::Proc::ObjectはProc::Simpleのサブクラスであり、24行目で引数の$codeを実行しているので、Proc::Simpleのインターフェースを見る限り、文字列で外部コマンドとオプションを渡すことができるはずです。

実験

実際に以下のようなテストを作って実験してみました。

$ cat hoge.sh 
sleep 3
echo "hoge"

$ cat t/12_shell.t 
use strict;
use Test::More;
use Test::Proc::Object;

my $proc = Test::Proc::Object->new('sleep 3 sec. Then print "done"', 'bash ./hoge.sh');

$proc->is_work;
$proc->exit_ok;
$proc->isnt_work;

$proc->stdout_like(qr/\Adone\z/);
$proc->stderr_like(qr/\A\z/);

done_testing;

これを実行すると、以下のようになり、テストは成功します。

$ perl -Ilib t/12_shell.t 
ok 1 - process sleep 3 sec. Then print "done" is work
ok 2 - process sleep 3 sec. Then print "done" exit code is 0
ok 3 - process sleep 3 sec. Then print "done" is not work
ok 4
ok 5 - process sleep 3 sec. Then print "done" STDERR like (?^:\A\z)
1..5

深掘りしていく

上記のことから、test_procにコマンドを文字列渡しして起動させることができそうですが、実際に以下のようなテストを試してみると、失敗してしまいます。

$ cat t/13_shell-with-test_proc.t 
use strict;
use Test::More;
use Test::Proc;

my $proc = test_proc 'my_work' => 'bash ./hoge.sh';

$proc->is_work;
$proc->exit_ok;
$proc->isnt_work;

$proc->stdout_like(qr/\Adone\z/);
$proc->stderr_like(qr/\A\z/);

done_testing;

$ perl -Ilib t/13_shell-with-test_proc.t 
Type of arg 2 to Test::Proc::test_proc must be sub {} (not constant item) at t/13_shell-with-test_proc.t line 5, near "'bash ./hoge.sh';"
Execution of t/13_shell-with-test_proc.t aborted due to compilation errors.

これはtest_procのインターフェースが($&)となっているために起こっている問題となります。ですのでこれを($$)としてあげると、以下のように成功します。

$ perl -Ilib t/13_shell-with-test_proc.t 
ok 1 - process my_work is work
ok 2 - process my_work exit code is 0
ok 3 - process my_work is not work
ok 4
ok 5 - process my_work STDERR like (?^:\A\z)
1..5

なぜgithub止まりなのか

このように、完成度の低さゆえにgithub止まりとなっているTest::Procですが、その他に大きな理由として、私個人が出くわす状況にそもそもテストスクリプトでプロセスの起動状態をテストしたいケースがあまりにも少ないという点が挙げられます。

そういうわけで、結局あまり洗練もされないまま5年もgithub止まりとして塩漬けになっていたというわけです。

Test::Procの今後

一応、ユースケース的に「いいじゃん?」って思ってくれる人がいらっしゃるのであれば、forkしてshipitするまでやってくだされば良いとおもいますが、今の所私自身がなにかをするということは無いでしょう。

コンセプトモデルということでここはひとつ、ご理解いただければ幸いです。

まとめ

ざっくりまとめると以下のようになります。

  • MetaCPANでPerlモジュールを公開する前に、いま一度そのモジュールの性質を見つめ直そう
  • Perlコミュニティの歴史から、誰かがつらい思いをしないための行いについて知ろう
  • Test::ProcというPerlモジュールを作ったけど、ユースケースに遭遇しなさすぎて結局Github止まりになった
  • Github止まりモジュールは「コンセプトモデル」として見ると良さそうかも

明日のPerlアドベントカレンダーは6日目。@yoku0825さんによる「マイエスキューエルにはPerl Mongerが必要かもしれないはなし」です。

Twitterアカウントを凍結されてしまいました。

様子です

しばらくはこちらのサブアカウント にて細々とつぶやいております。

ちなみに本日は私の誕生日でした。最高のプレゼントをありがとう、Twitter!

状況

Azure関連でちょっとした疑問があったので、 @AzureSupport に質問をしたところ、

と言われました。

言われたとおりに質問内容と、サポートに必要なID情報をDirect Messeageにて伝えたところ、suspendedとなってしまいました。

見解

今回suspendedとなってしまった原因は “Violating our rules against evading permanent suspension.” だそうです。

原因については「なるほど?」という感じですが、そもそも今回の経緯から、Twitter社はDirect Messageを覗き見しているという事がわかりました(利用規約にはその旨が書かれている)。

おそらくコンテキスト等を無視して、固有情報らしきものが流れてきたら簡単なチェックを経て、必要に応じてsuspendしているのではないでしょうか。

今後

不服申立てはすぐに行いました。が、仮にアカウントが復活しても、あまりアクティブな利用を行わず、そのままmastodonあたりに流れるかもしれません。

翌日

アカウントの解凍が行われました。Twitter社の担当部門の皆様、大変お手数をおかけしました。

Twitter社においては、認証済みアカウントとのやり取りにおける秘匿情報の取扱いについて、検閲・凍結のルールを見直していただくことを願う次第です。

200ミリ秒の検索APIをMicrosoft Azureでデザインする

200ミリ秒の検索APIとは

ここでは、httpクライアントからリクエストを受けてから全文検索を含む何らかの検索処理を行い、httpクライアントにレスポンスを返すまでの処理を、おおむね200ミリ秒(200ms、0.2秒)前後の時間でこなすWeb APIを指します。

検索という機能を考えたとき、200msという数字はまずまず軽快な応答速度ではないかと私は考えます。

今回はAzureの各種サービスを使って検索APIを作った時のノウハウを共有します。このAPIはデータサイズによって多少のばらつきはあるものの、ほぼ200ms前後の応答速度を実現することができております。

以下は、実際の処理履歴となります。おおむね200ms前後で処理が完了し、応答していることが分かると思います。

ログ

Microsoft Azureで作る

Microsoft Azure(以下Azure)で検索APIを作るにあたり、以下のようなシステムデザインを行いました。

システムデザイン

ある程度Azureに詳しい方であれば、上記の図を見ただけで概ねの構成がご理解いただけると思います。

Azure CDN

いわゆるCDN(Contents Delivery Network)サービスです。今年から動的コンテンツの高速化にも利用できるようになりました。

今回のケースでも「動的コンテンツの高速化」がその利用目的となります。

Azure Functions

実際にhttpリクエストを受け付け、httpレスポンスを返すためのアプリケーション・ロジックをデプロイし、運用するためのFaaSとなります。最近v2がリリースされましたが、私のケースではv1を利用しました。

C#やF#、PHPなどの言語に対応していますが、今回はNode.jsをつかって作ってみました。実際の実装・はまりどころについては前回のエントリにまとめてありますので、そちらも併せてご参照ください。

Azure Search

Apache LuceneクエリやODataフィルタを利用可能な検索エンジンサービスです。

全文検索や緯度経度をもとにした距離検索、ファセットなどにも対応しているため、複雑な検索機能を実装するにはほぼ必須となります。

また、Azure CosmosDBやAzure Table Storageなどから定期的(最短で5分おき)にデータを取り込むIndexerという仕組みがついてきますので、検索結果にリアルタイム性を求めない限り、データのインポートをする仕組みを自分で作る必要がありません。

Azure CosmosDB (または Azure Table Storage)

どちらもスキーマレスなデータストアとして利用できるサービスです。

今回はAzure CosmosDBを利用し、Azure Search向けの一次データをストックしておくデータストアとします。

まとめ

200ms前後の応答速度の検索APIをAzureで実現するシステムデザインを紹介しました。

「えっ、実際の作り方は書かないの?」という声が聞こえてきそうですが、その辺は公式ドキュメントや有志のブログに書いてあることしかやっていません。

強いてあげるとすれば、前回のエントリで書いたようなはまりどころがあるくらいですので、そちらを見ていただきたいと思います。

参考にしたドキュメント・ブログ

[Azure + javascript]Functionsのhttp triggerな関数でSearchの検索結果を返す

暖簾に腕押し

私がAzure Functionsに提供してほしいと切に願っている機能の一つに、Search Bindings(仮称)があります。なお、私がこの機能実装のリクエストを出しました。

しかし、現時点ではこのような機能が提供される気配は全くなく、付帯するロジックを記述するしかありません。暖簾に腕押し、というやつです。

実際、このようなロジックを書くことすら億劫なのですが、嘆いていても仕方がないですので、javascriptからAzure Searchへ問い合わせを行うnpmライブラリazure-searchを利用し、レスポンスを返すところまで実装することにしました。

役割

  • クライアント : webAPIを利用し、HTTP Responseに含まれた検索結果を受け取ります。

  • Azure Functions : クライアントからHTTP Requestを受け取り、Azure Searchに問い合わせをし、結果をクライアントに返します。

  • Azure Search : クエリをFunctionsから受け取り、検索結果を返します。また、Indexerという機能を利用し、定期的にTable Storageからデータを同期します。

  • Table Storage : 検索対象となるデータがストックされています。Indexerによって、定期的にデータ参照されます。

データ構造

検索対象となるデータ構造は以下のようなものです。

[
  {
    "PartitionKey": "2018-08-29:myroom",
    "RowKey": "ecd53616-e756-41fb-98d2-fe2b387e0c8a",
    "id": "ecd53616-e756-41fb-98d2-fe2b387e0c8a",
    "channel": "myroom",
    "body": "5000兆円 欲しい!!!",
    "author": "ytnobody",
    "visible": true,
    "timestamp": 1535508299
  },
  ...
  ...
]

PartitionKeyおよびRowKeyはいずれもTable Storageで必須の項目です。(参照:Azure ストレージ テーブルの設計ガイド: スケーラブルな設計とハイパフォーマンスなテーブル

Search側のスキーマ構造

インデックスmessageには、Table Storageに格納されているデータ構造を、ほぼそのまま持ってきています。Table Storageで利用していたPartitionKeyおよびRowKeyはここでは使いません。

  • id Edm.String (key, retrievable, searchable)
  • channel Edm.String (retrievable, filterable)
  • body Edm.String (retrievable, searchable)
  • author Edm,String (retrievable, filterable)
  • visible Edm.Boolean (retrievable, filterable)
  • timestamp Edm.Int64 (retrievable, filterable, sortable)

普通に実装

最初、以下のように実装しました。

// function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "disabled": false
}
// index.js

module.exports = function (context, req) {
    // Searchクライアント初期化
    const AzureSearch = require("azure-search");
    const client = AzureSearch({
        url: process.env.SEARCH_URL, 
        key: process.env.SEARCH_KEY
    });

    // 検索ワードをスペースで切って配列にする
    const words = req.query.word ? req.query.word.split(' ') : [];

    // ページング指定
    const page = req.query.page ? parseInt(req.query.page) : 1;
    const top  = req.query.size ? parseInt(req.query.size) : 10;
    const skip = top * (page - 1);

    // 検索オプション
    const search = words.map(w => `body:${w}`).join(' AND ');
    const filter = 'visible eq 1';
    const searchOptions = {
        queryType:  "full",
        searchMode: "all",
        top:        top,
        skip:       skip,
        search:     search,
        filter:     filter
    };

    // 問い合わせ
    client.search('message', searchOptions, (err, results) => {
        context.res = err ? {
            status:  500,
            headers: {"Content-type": "application/json"},
            body:    {"message": `Internal Server Error: ${err}`}
        } :
        {
            status:  200,
            headers: {"Content-type": "application/json"},
            body:    results
        };
        context.done();
    });
};

察しの良い方なら気づいたかもしれませんが、このロジックは期待通りには動かず、502エラーを返してしまいます。

何がダメなのか

期待通りに動かない原因は、client.search(...)の結果を受け取る前にmodule.exports自体が処理を終えてしまうからです。

レスポンスらしいレスポンスを設定しないまま処理が終わってしまうので、502エラーを返す、ということです。

対応方法

結論から書くと、以下の2点を直すと良いです。

  1. module.exportsを、Promisereturnするように変更する。
  2. function.jsonにて、http output bindingsのname$returnにする。(portalの場合、応答パラメータ名のところにある「関数の戻り値を使用する」をチェックする)

なおしてみる

なおした後の実装がこちら。

// function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ],
  "disabled": false
}
// index.js

module.exports = function (context, req) {
    // Searchクライアント
    const AzureSearch = require("azure-search");

    // 検索ワードをスペースで切って配列にする
    const words = req.query.word ? req.query.word.split(' ') : [];

    // ページング指定
    const page = req.query.page ? parseInt(req.query.page) : 1;
    const top  = req.query.size ? parseInt(req.query.size) : 10;
    const skip = top * (page - 1);

    // 検索オプション
    const search = words.map(w => `body:${w}`).join(' AND ');
    const filter = 'visible eq 1';
    const searchOptions = {
        queryType:  "full",
        searchMode: "all",
        top:        top,
        skip:       skip,
        search:     search,
        filter:     filter
    };

    // 問い合わせ
    const promise = Promise.resolve(AzureSearch({
        url: process.env.SEARCH_URL, 
        key: process.env.SEARCH_KEY
    }))
    .then(client => client.search('message', searchOptions))
    .then(results => {
        return {
            status:  200,
            headers: {"Content-type": "application/json"},
            body:    results
        };
    })
    .catch(err => {
        return {
            status:  500,
            headers: {"Content-type": "application/json"},
            body:    {"message": `Internal Server Error: ${err}`}
        };
    });

    return promise;
};

function.jsonでは、http output bindingsのname$returnとなっており、functionの戻り値をレスポンスに使う設定となっています。

そしてindex.jsではPromise.resolve(...).then(result => ...).catch(err => ...)の形式でSearchに問い合わせを行った後の処理をハンドリングするよう定義し、promiseそのものをreturnするロジックへと書き換えられました。

本当はドキュメントに書いておいて欲しかった、もしくは・・・

実はPromiseをreturnすることで解決できるという事について、公式ドキュメントには書かれていないようです(ソース。openになっているし、書こうとはしてる模様)。

今回の解決法は、上記issueを辿ってようやく見つけることができたものでした。

この手の手間をなくすためにも、Search Bindingsが欲しいな、と思うのでした。

まとめ

  • Search Bindings欲しいですね。

2018-08-30 追記

node-azure-searchでPromise/thenを使った書き方では、検索結果のヒット件数を取得することができないという問題がありました。

const AzureSearch = require('azure-search');
const word = '5000兆円';
AzureSearch(...)
    .then(client => search({
        queryType:  "full",
        searchMode: "all",
        top:        20,
        skip:       0,
        search:     `message:${word}`,
        filter:     'visible eq 1',
        orderby:    'timestamp desc',
        count:      true // <--- @odata.countをレスポンスに含めるための指定
    }))
    .then(rows => {
        // rowsは検索結果(オブジェクト)が入った配列。
        // ここで検索結果のヒット件数である@odata.countを利用したいができない!!
    })
    .catch(err => { ... });

これはnode-azure-searchのindex.jsを修正することで、取得できるようになります。(ただしインターフェイスを破壊する変更です)

@@ -492,7 +492,7 @@ module.exports = function (options) {
            return new Promise(function (resolve, reject) {
              args.push(function (err, value, data) {
                if (err) reject(err)
-               else resolve(value)
+               else resolve(data) // resolve(value) not contains '@odata.count'
              })
              fn.apply(self, args)
            })

利用する側は以下のようになります。

const AzureSearch = require('azure-search');
const word = '5000兆円';
AzureSearch(...)
    .then(client => search({
        queryType:  "full",
        searchMode: "all",
        top:        20,
        skip:       0,
        search:     `message:${word}`,
        filter:     'visible eq 1',
        orderby:    'timestamp desc',
        count:      true // <--- @odata.countをレスポンスに含めるための指定
    }))
    .then(result => {
        const count = result['@odata.count'];  // 検索結果のヒット件数。
        const rows  = result['value'];         // rowsは検索結果(オブジェクト)が入った配列。 
        ...
    })
    .catch(err => { ... });

破壊的な変更であるため、forkして利用しています。

Hokkaido.pm #14で 予想の話 というか 予想を支えるシステム設計の話 をしてきました

予想の話予想を支えるシステム設計の話

札幌で開催されたHokkaido.pm #14というイベントに参加し、予想の話(というか予想を支えるシステム設計の話)をしてきました。

内容としては「おとなの自由研究」の取り組みの中で作った南関競馬予想システム「うまミる」のシステム構成と予想ロジック、運用などについてを話したのですが、30分は予想以上に短く、あらかじめ40分で発表枠を押さえておけばよかったというのが正直な感想です。

うまミるについてより詳しく聞きたい方は、アルコールチャンスを用意してくださればお話しますので、是非ともよろしくお願いします。

Hokkaido.pmの皆さん、本当に楽しかったです。ありがとうございました。出来たら11月くらいにもう一発くらい開催してくれたらいいのにな、って思いました!

あと当然ながら、初夏の札幌を満喫したのは言うまでもありません。

Gotanda.pm #18 六本木編 でLTしました

Gotanda.pm #18 六本木編で、Core Modulesに寄せてみたら?というタイトルでLTしてきました。

オフィスから歩いていける場所ということもあり、割と早々とLTすることを決めたんですが、勢いは大事ということでLTもどうにか勢いを維持して発表できたんではないかなと自負しております。

「システムプログラミング」というテーマだったようでして、 strace を使ったperlのopenとシステムコールの結びつきについて詳説したトーク(kazeburoさん)や、ネタが被ったと言いながらも、別の切り口によるディープなトーク(skajiさん)、はてなブログSSL化の裏側についてのトーク(papixさん)など、全体的に「掘り下げられた発表」が多かったように感じました。

Mishima.pm #3 に参加してきました

Mishima.pm #3というイベントが6/3(日)に開催されました。

主催の@dokechinさんが三島在住ということで、数年前から不定期的に開催されていまして、私は #0, #1, そして今回の #3 と、開催されるたびにできるだけ行こうと思っているpmの一つです。

今回の参加者は私を含めて4名。当日の様子は#mishimapmtogetterにもまとまっていますので、そちらも併せてご覧いただけると雰囲気が伝わりやすいのではないでしょうか。

「Why people says “Perl is guilty”?」というタイトルで発表

こちらに発表スライドのMarkdownを置いてありますので、そのままご覧いただくか、reveal.jsで任意のテーマを当ててご覧ください。

この発表で言いたかったこと

かなりポエムっぽいことなのですが・・・

様々な言葉でPerlを貶める人はいるし、私も彼らの言っていることにも一定の理解を示します。確かに一部は事実でしょう。

しかしそれらは大抵、他の言語で鳴らしてきた人が、大して知りもしないPerlについて語っているだけではありませんか?少なくとも私にはそう見えます(私がPerlのすごい人かと言われると、そんなこともないんですけど)し、悲観的すぎるだろうと思います。

そんなことを気にするより、低い理解度でも自分の思った処理を書くことができることの方が大事だと思いますし、Perlにはそのためのツールセットが揃っているんだ、と言いたい。

実は覚えることがかなり少なくて良いので(例えば変数記号については$と@と%を理解できればひとまずOK)、まず書いてみて、足りないところの知識を補いながら少しづつ上達するのに向いている言語なんです。

斜に構えて100%理解した風に振る舞うより、まず手を動かして試すところからPerlを触ってみると、意外といいものだと思えますよ。

・・・スライドからは読み取れないでしょうけど、こんな感じのことを話しました。

コンテキストの観点でPerlを理解する

私の発表の前に@karupaneruraさんの発表内容が、Perlのコンテキストに関する話題に触れていました。

例えば$age = 100というデータについて、

  • $age . "歳のおじいさん" という文字列的な扱い
  • $age + 1 という数値的な扱い

の両方を同じ変数に対して行えることは、コンテキストを意識することで、一見複雑に思える挙動を理解できるという感じの話でした。

似たようなことをやっている言語はLispやFORTHなどが挙げられます。

この考え方のメリット・デメリットについて書くと長くなるので別の機会に回しますが、Perlについての理解を深める視点として、非常に慧眼だなと感じました。

三島を満喫する

うな丼

三島で八王子

竹倉温泉の待合室

新幹線コンコースから望む富嶽