Null と値型のただ2つからなる Union 型を Maybe という特別な型として推論するようにした。ちょっと便利。
Maybe だったら {"type" : ["null", "string"]}
のように出力するようにすると、より厳密な JSON スキーマを生成することができるようになった。
ほか JSON::TypeInference::Type::* にドキュメントを追加するなどしました。
Minilla を使っている。minil new
してからちょっと手を入れる。
<aereal@aereal.org> <aereal@users.noreply.github.com>
GitHub の Web インターフェースで Merge Pull Request ボタンを押すとコミットの AUTHOR_EMAIL がこれになる。
たぶんメールアドレスを非公開にしているとこれになるような気がする。悩ましい。
sudo: false # Use container-based environment language: perl perl: - "5.12" - "5.14" - "5.16" - "5.18" - "5.20" cache: directories: - local # Caches installed modules before_install: - which carton || cpanm --notest Carton - carton version install: - carton install script: - make -f ci.mk cover after_success: - make -f ci.mk coveralls
local/
) をキャッシュに含める……をやっている。
テストで実行するコマンドを Makefile にまとめる。
CARTON_EXEC = carton exec -- COVER_IGNORE_RE = ^t/|^local/ test: $(CARTON_EXEC) prove t/ cover: $(CARTON_EXEC) cover -test -ignore_re '$(COVER_IGNORE_RE)' -make 'make -f ci.mk test' coveralls: cover $(CARTON_EXEC) cover -report coveralls
Perl の値 (スカラ値、ハッシュリファレンス、配列リファレンスなど) が JSON のどの型であるかを推論するモジュール。
Perl の 1
という値は JSON の number である、Perl の "a"
は JSON の string である、という風な。
配列やハッシュのようなコンテナ型も再帰的に推論する。[1,2,3]
のような値を与えると array[number] という型である、と推論する。
候補となる型が複数ある場合は直和型 (union type) として報告する。すなわち [1, 'a']
のような値は array[number|string] と推論する。
もともとこのモジュールは JSON schema を自動生成することを目指して作った。
JSON schema は JSON over HTTP API を提供するときに便利な仕様群であるものの、JSON schema それ自体を記述するのはリーズナブルとは言い難い。
これはソフトウェアが再利用することを意図しているので仕方のないことではある。とはいえ書くのはだるい。
ずっと { "type": "object", "properties": {} }
と書いていると気が滅入る。
JSON schema を実際の JSON データを (複数) 与えて自動的に型や構造を出力してくれると便利そうである。
実際、そのようなことをしてくれるライブラリはある。
(参考: JSON Schema Software)
しかしながら当然 (?) Perl のライブラリは紹介されていない。CPAN でよさそうなそれらしいモジュールは見つけられなかった。なので書くことにした。
このモジュールはその一歩です。
JSON::TypeInference を使って実際に与えられたデータから schema を自動生成するスクリプトを書いてみた。
README にあるように eg/
以下の JSON からこんな schema が生成される:
{ "$schema" : "http://json-schema.org/draft-04/schema#", "description" : "TODO", "properties" : { "id" : { "example" : 1, "type" : "number" }, "name" : { "example" : "yuno", "type" : "string" }, "school" : { "properties" : { "location" : { "properties" : { "lat" : { "example" : 14, "type" : "number" }, "lon" : { "example" : 15, "type" : "number" } }, "type" : "object" }, "name" : { "example" : "yamabuki", "type" : "null" } }, "type" : "object" } }, "title" : "TODO", "type" : "object" }
union type の扱いが雑なのは将来的に直したい。
あるオブジェクトのプロパティが必ず出現すべきなのかどうか、も推論できるとよさそうと思って issue を立ててある。
Detect required properties · Issue #5 · aereal/JSON-TypeInference · GitHub
ちょうどいいデフォルト値が見当たらないので、このキーは各環境で適宜、定義せよ、という風にしたい。interface みたいなかんじ。
use strict; use warnings; use Test::More tests => 2; use Test::Fatal qw(exception); { package My::Config::NotImplemented; use strict; use warnings; use Scalar::Util qw(blessed); sub new { my ($class) = @_; return bless {}, $class; } sub equals { my ($self, $other) = @_; return blessed($other) && $other->isa(ref($self)); } }; { package My::Config; use strict; use warnings; use Config::ENV 'MY_ENV'; use constant NOT_IMPLEMENTED => My::Config::NotImplemented->new; sub declare ($) { my ($name) = @_; return ($name => NOT_IMPLEMENTED); } common +{ declare 'log.path', }; config production => { 'log.path' => '/var/log/app.log', }; config test => +{}; }; { package Test::My::Config; use strict; use warnings; use Config::ENV (); sub import { my ($class) = @_; no strict 'refs'; no warnings 'once'; *My::Config::param = \¶m; } sub param { my ($class, $name) = @_; my $value = Config::ENV::param($class, $name); return !My::Config::NOT_IMPLEMENTED->equals($value); } }; Test::My::Config->import; my $envs = do { my $envs = My::Config->_data->{envs}; [ keys %$envs ]; }; for my $env (@$envs) { local $ENV{MY_ENV} = $env; my $current = My::Config->current; for my $key (keys %$current) { ok +My::Config->param($key), "Environment ($env) defines `$key'"; } }
declare($name)
で各環境で定義すべきキーを宣言する。これは対応する値として意味のないオブジェクトを便宜上定義する (Config::ENV は設定値として hashref を期待するので)。
テスト時に Config::ENV を継承したクラスの param($name)
を上書きして、値が NOT_IMPLEMENTED オブジェクトかどうかをアサートする、という風にする。
すると # Failed test 'Environment (test) defines `log.path''
という風にテストが失敗し、たしかに test で log.path が定義されていない、ということがわかる。
JSON schema とか XML とか使うともっとリッチな検証ができそうだけど、Config::ENV でも素朴にできるというコンセプト。
トークしつつ、トークを聞いたりなどした。
いろいろ聞いたけど印象的だったトークについて。
kazuho さんの発表を初めて見た。声が高い。
僕は大学を中退していてアカデミアどころか学部の卒業論文がどういう雰囲気なのかよく知らないけれども、論文やベンチマークなど根拠となるデータが必ずあってそれを軸に話がなされるので、けっこう速いペースにも関わらずすんなり聞くことができた。
アセットコンパイルを行う動機はリクエスト数を減らすだけではないので必ずしも HTTP/2 によってアセットコンパイルという考え方が無用の長物になることはないだろうと思った *1。とはいえ、言及されたように考え方を変える必要はあるだろうと思う。
言ってしまえば高々60分に満たない時間で HTTP/2 について一本筋の通った理解が得られたのは、事前に断片的な知識が自分にあったことを差し引いてもすごいことだな、と思った。
また、発表の内容自体もさることながら、自分の中で「わかりやすい」「聞きやすい」プレゼンテーションの具体例がひとつできたのも尊い。
WebAudio と信号入門の話。
「今まで Web エンジニアはディスプレイという限られた光情報しか操ることしかできなかったが、WebAudio によって空気を振動させディスプレイを越えて影響を及ぼすことができる」という風なことをおっしゃっていた。
少しおどけた風だったような気がするけれども、実際、胸を打たれた。僕はわりと音楽が好きで音が出ると喜ぶので WebAudio が Web API に入るときはおもしろそうだ、と思ったけれどもそれ以上のことは考えられなかった。
音波とか音響に関する知識はわずかながらあったけれども、信号の話は不明だったのでとてもおもしろかった。とてもエンターテイナーだなあ、と感じた。
タイトルにある通り Go におけるプロファイリングおよび最適化の話で、それはそれでもちろん参考になったけれども、思いがけずライブコーディングを見ることができてとてもよかった。
2012年にチケットを買っていて行くつもりだったけれどもぎりぎりでやめて、別の人に譲った。2014年も参加しなかった。どちらの年もトークを応募しなかったから。
自分はエンジニアで普段からインターネットなどを通じて情報を得たり OSS コミュニティからソフトウェア資産を享受し (つつ、わずかながら還元し) ており、では YAPC のようなカンファレンスで自分が果たせる役目とはなんなのかと問うてみると、おもしろトークをすることだと思っている。
また、そうやってトークをするという形で還元することをイベントに参加する条件として課しているのは「お客様気分」を持たせないためでもある。YAPC は有料のイベントなのでなおさらである。
YAPC::Asia Tokyo にて2回スピーカーとして登壇できたことはとても褒まれ高いことだと思っている一方で、YAPC::Asia Tokyo に区切りがついたとしてもそういったコミュニティへの還元への考え方は変わることはないだろう。
おもしろトークのために技術を磨いていかねば。
みなさまお疲れさまでした。
*1: たとえば browserify などはブラウザにおける JS 実行環境においてモジュールシステムがないことに対するアプローチである
世界展開する大規模ウェブサービスのデプロイを支える技術 - YAPC::Asia Tokyo 2015
2年ぶり2度目の YAPC::Asia Tokyo でタイトルにある通り発表をしてきました。資料は近日公開予定です。
立ち見続出のようで満員御礼といった具合で非常に嬉しいしありがたいことです *1。
懇親会などで「おもしろかった」と声をかけてもらえることが多くて非常に嬉しかったです。 「おもしろかった」「最高」と思ったらぜひ ベストトークに投票 してもらえると嬉しいです!!!
*1: もっとも立ち見されていた方は60分近いトークだったので大変だったでしょうが
YAPC::Asia Tokyo 2015 も来週に控えて、どういうトークを見にいくか悩むこの頃なのでカレンダーをつくりました。
トークリストから素朴にスクレイピングして作ってあります。organizer にスピーカーの名前が入れられるとよかったのですが、メールアドレスが必須で手頃に入手できそうになかったので諦めてイベントのサマリに入れてあります。
Google Calendar などにインポートするなどして、どうぞご利用ください。
gulp とかを使っているプロジェクトの場合、ビルドツール類も devDependencies
に含めてバージョンを固定したいという要求があると思う。
ところが実行ファイルにパスを手軽に通したいという理由のみで npm install -g gulp
などしてしまうとバージョンが固定できなくなってしまい、本末転倒である。
とはいえ ./node_modules/.bin/gulp build
してくれ、というのも面倒であるので、どうするとよいのか書いておく。
たとえば direnv を使う。
# .envrc export PATH="$(npm bin):$PATH"
direnv allow
.envrc は作業ディレクトリを移動した時に評価されるので、npm bin
の出力が異なる環境でもそれぞれうまく動く (はず) なのでリポジトリに含めてもよい。
package.json の scripts
フィールドに定義したタスクは npm run NAME
で実行できる。
また、この scripts
フィールドに定義したタスクの実行時には node_modules/.bin
にパスを通した上で実行される。
In addition to the shell's pre-existing PATH, npm run adds node_modules/.bin to the PATH provided to scripts.
run-script | npm Documentation
統一されたインターフェースを用意するという意味でも scripts
フィールドは望ましい。
direnv と scripts フィールドは役割が少し異なるので、どちらか一方のみを利用するということもなく両方活用できるとよさそうですね。
スコアメーカーという楽譜入力ソフトがあってこれを買ってもらって遊びはじめたところ、MIDI というもので勝手にコンピュータに演奏させることができるとわかって大喜びしはじめた。
ひたすら入力して演奏させる。アーティキュレーションが思ったかんじではないので調整してまた聞く、の繰り返しを土日のあいだ飽きることもなくやっていた。
小学生を卒業したころだったかもしれないけど、ヤマハがミッドラジオプレイヤーというソフトを公開した。
ミッドラジオプレイヤーはソフトウェア音源を内蔵していて、スコアメーカーに再生させるよりずっとリッチな音色になるし、なによりそれが無料で使えるのでヤマハ最高! と唱えながら毎日使っていた。
その後に MP3 というものの存在を知ることとなる。MP3 はダウンロードに時間がかかるけどなにやらきれいな音で MIDI が聞ける、という理解のものだった。
後に MP3 は音声を録音するためのフォーマットのことであり、自分が言っていた MP3 はいいソフトウェア音源の演奏を録音したものだと知る。わからなかったことがわかってよかった、と思う一方で、自分が MP3 を作れたとしてもソフトウェア音源を買わなければいけないことを知ってがっかりした。
しかし自分にはミッドラジオプレイヤーがあった。ミッドラジオプレイヤーで再生してそれを録音すればいいということを思い付いたときは自分が天才かと思った。
作った曲はヤマハの音楽投稿サービス (もうなくなった) に投稿していた。
打ち込みしてるうちにいい曲ができたので、中学生にあがってから朝日作曲賞に応募したことがあった。
打ち込みしていたのは悪夢っぽくはないけど、できた曲のことを思い出すと悪夢っぽい。
これに似た問題。
tmux のセッション内だったので試しに reattach-to-user-namespace electron ...
としたらうまくいった。
新しい MacBook Pro がやってきたのでセットアップの記録を書いておく。
「だいたい」とあるように、実際のところ自分の手を動かさざるを得ない手順はまだまだあるし、むしろ増えたりもしている。
System Preferences
→ Security & Privacy
→ Firewall
からファイアウォールを有効にするSystem Preferences
→ Sharing
を開く
Computer Name
を変更する (今回は izanagi
にした)Remote Login
を有効にして sshd を起動するSystem Preferences
→ Keyboard
→ Modifier Keys ...
から caps lock を control に (後述するが OS X におけるこの修飾キーの設定の扱いは思ったよりも込み入っていたので手作業することにした)additional componentsと以下をインストール:
System Preferences
→ Security & Privacy
→ Privacy
→ Accessibility
から Karabiner を許可する今回、スクラッチから書いてみた。
以前にも Playbook を書いて公開しているのだけれども、Ansible の雰囲気に慣れながら書いていったものなので「今だったこう書くな・こう分けるな」と思っていたことと、Playbook に書いていた設定やインストールすべきソフトウェアにけっこう手垢がついており本当に必要な設定・ソフトウェアだけに絞りたい、という目的があってスクラッチから書いた。
また、前に書いた Playbook では別のホストでも実行できるような汎用性を持たせようとしていたけれども結果的に破綻して実現できていなかった反省とそもそもそのような汎用性は用途上不要だと考えて、今のマシンに特化させることにした。
書く時に考えた果たすべき冪等性の基準として「変更を加えない限り何度実行しても失敗しないこと」を掲げた。
「失敗しないこと」が意味するものは、Ansible の出力が failed にならないこと。
Ansible では task を実行した結果、副作用が及ぼされると changed というステータスになる。
Ansible のコアモジュールでは設定で記述した状態に既に収束していた場合、変更は実際に行わないように書かれている。
また、「変更を加えない限り」というのは、たとえば ssh でログインしてディレクトリが既に作られている場合などを特別に考慮しない、ということ。だいたいは Ansible のコアモジュールがカバーできるし、それらでカバーできない場合はおそらく自分でひとつモジュールをひとつ作るに相当する労力をかけることになるので、それはコストに見合わないと考えた。
また、前提条件をはっきりさせておかないと考慮すべき条件が爆発するという問題もある。
上記 stackexchange のスレッドに書いてあることがすべてだが:
……ということらしく、けっこうめんどうそうだったので諦めて手作業することにした。
Path::Tiny には new
という、いかにもオブジェクト指向的なコンストラクタのような名前のメソッドが定義されているのでいかにもオブジェクト指向的に継承できるように見えるが結論から言うと できない 。
sub new { shift; path(@_) }
Path-Tiny/Tiny.pm at 37c371dbd100d82968b57bafc52222463ceb53bd · dagolden/Path-Tiny · GitHub
This is just like C
, but with method call overhead. (Why would you do that?)
とあるので、ドキュメントにある通りとも言える。
どうしても継承したい場合は再度 bless
を呼べば上書きされる。
package My::Path; use parent qw( Path::Tiny ); sub new { my ($class, $path) = @_; my $self = $class->SUPER::new($path); return bless $self, $class; }