PHPのarrayをリストとして使うときはunsetしちゃダメという話

タイトルそのまんまです。

PHPのarrayは順序付き辞書なんだけど、リストに見えちゃうのでついついやってしまうのが $a[count($a)-1] こういうの。善良なプログラマほどこの罠にかかるから、こういうときはunsetじゃなくてarray_spliceを使おうね。

PHPのinterfaceなめんな

はいタイトルは釣りです。

OOPのインターフェースはただの実装漏れチェック機能じゃないし、ましてや継承は差分プログラミングツールじゃないぞ。というのはわりと一般的な話だけど、Ruby(respond_to?でホントにいいの)とJava(インターフェースが自然すぎてユーザが意識しないのよ)が、PHPに対してOOPどうこうで偉そうに言うのはどうかなと思ったもので。まあそれと同時に、PHPの人自身がその意義を発見してるのかなという疑問もあったりしたんですけどね。

Rubyというのは「オブジェクト指向ってのはつまりメソッドに応答できるアヒルはみんなアヒルとみなせるよね」というレベルのダックタイピングで割り切った言語だと、個人的に認識しています。継承とミックスインにはis_aが応答するけど本質はrespond_to?のほうで、インターフェースを宣言してなくてもメッセージ送れたらいいあの感じ。

そんなRubyでオブジェクトが特定の機能の「セット」を実装しているかを調べて、アヒルかどうかを分岐するコードはこんなふうになります。

めんどくさいですね。おっと、ここで継承かミックスインで解決してis_aを使えばいいというのはナシです。単一継承の言語における継承はそうそう安易にできるものではないですね。OOPで設計してる人なら、本質的に意味の違うものを同じ継承ツリーの枝に入れないといけないとき、辛い思いをしたことは何度となくあるはず。

ミックスインなら同じ継承ツリーじゃない、ってことでミックスインをマーカーに使おうとするとこんな感じ。

機能的にはだいぶいいけどちょっとだけ残念。でも「ミックスインは機能の混ぜ込み」だという本来のコンセプトに反した使い方なので、人にわかってもらえにくい気がします。

いっぽう、Javaではインターフェースとメソッド実装のコピペ自動生成を分けてやることで、オブジェクトに複数の顔をもたせられます。まあその代わり、メソッドに渡す型の制約が強すぎて、アヒルを扱うにはいろいろめんどくさい子です。でもたまに型がRubyお得意のダックタイピングに勝ることもあります。

いまこれRubyのダックタイピングの例と同じ意味のことをやってるんですが、そうは見えませんね。ここちゃんとやり始めると、MLとかそっちの「型」の本質の話になってしまって、PHPをdisる人よりもっと恐い人が来るのでやめときます。分岐をコンパイル時ではなく実行時に持ってくると、こうなります。

だいぶLLっぽくなりました。インターフェースがくっついているかどうかを分岐の根拠にしたりするテクニックはマーカーインターフェースと呼ばれ、マイナーですがあります。ああでもやっぱ、型のある言語の本質は前のやつが何も意識しなくてもできることなんですよねホント... いいなぁ。

さてさて、PHPだとどうなるか。Javaのコードは手抜きしちゃいましたが、ここが本題なのでというわけで:

PHPさん意外にも超スッキリ!

PHPみたいに変数に型がない(値に型があるのとは違う意味ね)言語におけるインターフェースというのは、Javaの教科書に書いてある、「実装の制約」と「入出力型のコンパイラチェック」用ではなくて、むしろJavaでは応用的なほうだと思われてる、マーカーの意味合いが強いと思うのです。PHP で instanceof があのグローバル関数のカオスに紛れていないファーストクラスの演算子だというのは、そういう意味なんじゃないでしょうか。たしかにアノテーションの記述性が高ければそっちでも良かったぐらいのものなんだけど、それに十分な表現力を持たせようとすると、必然的にJavaのinterfaceみたいになるので、偶然にもPHPの選択は正解だったとようやく最近思っています。

たしかに、Javaみたいにコンパイル時に型の分岐が済んでいるのはありがたいのですが、オブジェクト指向以外に型理論まで意識しないといけないのは、楽しい楽しいプログラミングの本質を遠ざけてしまうと思います。これはずっと最初からRubyが教えてくれたコンセプトだと思うし、だからこそLAMPブームが起こったわけで、なので、安直に型に支配されるほうが良いとはあまり言いたくないです。(OCamlScalaの人がなんか言いそうですが、型推論は記述量を減らせても型の本質的高度さから逃れることはできないと思うのでちょっと...)

PHPのコードでもっとも大事なのは、

  • 抽象クラス => インターフェース + 実装差分
  • インターフェース + 実装差分 => 抽象クラス

というセオリーが成り立ってるということです。だだ綺麗なのが目的じゃなくて、意図が明確でトリッキーじゃないというのが大事。

僕は抽象クラスの継承がインターフェースと実装差分に分けて考えられないのは、ちょっと不健全な気がするんです。そういうのって、結局継承システムがその本質である「概念の抽象レベルの階層化」に使われず、ただの実装差分としてしか意識されなくなって、ロジックさえ全部書いてあればいいんじゃないかという錯覚に陥る危険性もあるんじゃ...という危惧があります。
やはり、継承とミックスインだけあってインターフェースがないとか、継承とインターフェースだけあってミックスインがないというのは、ちぐはぐな感じがするんですね。普通に生産的で使いやすいオブジェクト指向言語というのは、差分プログラミングするかどうかと、APIインターフェースの設計をどうするかという問題とは、直交しているべきだと思います。

たしかにPHPも、5.3まではJavaのようにちぐはぐでした(おっと5.3未満のことは言うな)が、5.4でトレイトが入ってくるので、やっとこれで役者が揃ったように思います。(クロージャという名のRunnableの無名インナークラスはすでに5.3で入ってますしね)


しかしarrayとリファレンスと文法パーザの作りはそれ以前の問題だ、という声が聞こえなくもないですが、たとえば何の取り柄もない幼馴染みがごくまれに上手にお菓子とか作ったらかわいいじゃないですか。

jEdit4.5.0の日本語リソースできました

jEditの4.5があっという間にfinalになってリリースされました。4.4preは何だったのかという...
まあ、早い段階でちょっと便利な機能が増えて安定版になってくれるのはありがたいことです。

さてそんなわけで4.5.0対応の日本語ファイル作りました。
https://github.com/tanakahisateru/jedit_gui-ja/downloads

前のからちょっと用語を変えました。禁則処理はワードラップに統一、文字を入力する文脈でカーソルと呼んでいたのはキャレットに変更してわかりやすく、という具合です。他にもいろいろと整合性がなかったり元の表現が変わったりしてたのを訂正しました。

ちなみに4.5はMac対応もけっこういい具合になってます。このバージョンから、Macで使うとショートカット用の修飾キーがあのMacのキーボードの絵になってくれてます。これはオシャレ。

で、せっかく本家がMacを意識してるので、こっちもやっとこうというわけで、Mac用の翻訳ファイルは別にしました。Macのメニューって、Windowsみたいな日本語でのキーボードニーモニックの代替手段が通じなくて「新規作成(N)(C+N)」の(N)のところがただの邪魔な文字になってしまうんですね。そこで、Mac用のファイルはニーモニック全部消しました。

CakePHP2のTwitterBootstrapプラグインがかっこいい

CSSフレームワークのような、Webアプリのフロント用ツールキットのような、TwitterのBootstrapがいい感じです。フォームのmargin/paddingの具合やプリセットの表現がほんとスマートで、やりすぎないのにすごく丁寧。

http://twitter.github.com/bootstrap/

そのBootstrapのかっこいいフォームのスタイリングなんかをCakePHPでさくっと使えるようにしたプラグインがこちらにあります。

https://github.com/slywalker/TwitterBootstrap

  1. このGitHubプロジェクトをまるごとCake2の app/Plugin/TwitterBootstrap に入れる。
  2. app/Plugin/TwitterBootstrap/webroot に bootstrap の js, css, img をぶちまけ。
  3. プラグインの中にある View/Layout/bootstrap.ctp と View/Element/alert.ctp を app/View の下の各所にコピーしてくる。

これであとは、app/Console/cake bake するとき増える適当なbakeテンプレートを選べばOK。コントローラとビューにそれぞれ、プラグインからbakeテンプレートが提供されます。

以下、ほんとに bake しただけのビューの周りをちょっとだけカスタマイズしただけのものです。

やったのはパンくずウィジェットを入れたぐらいで、アプリケーションロジックっぽいことはいっさいやってませんが、なんだか頑張った感があってかっこいいですね。もっとがんばったら、というか、あまりがんばらなくても、Bootstrapのプリセットのスタイルやアイコンを引っ張り出して来るだけで、もっとオシャレな感じにもなると思います。

エラーまで勝手にかっこよくなりました。

余談: あとで app/Config/bootstrap.php を開いてHTMLをデザインしようとしてああってなりました。ファイル名が似てるけど間違えないように注意しましょうオレ。

GitHubでプルリクエスト用ブランチを保守するメモ

GitHubにリポジトリを置いてる人はみんなプルリクエストを待っています。けどプルリクエスト用にフォークした自分のリポジトリを保守する方法が途中でわからなくなって...という人が案外多いんじゃないかなと思ったり。なので、ちょっとメモ置いときます。って、人のためみたいな言い方ですが、まあ自分用のメモです。

まずこうしたほうがいいという原則。masterブランチはフォーク元から変更せず、かならず自分用のブランチを作る。これは、masterを作業の同期用に置いておくためです。

自分のブランチでコミットしたあと、フォーク元のmasterが進んでないかのチェックは必ずすること。
もし進んでいたら自分のmasterに元作者のコミットを取り込んで自分のGitHubでのフォークが最新と同期してる状態にしましょう。で、元作者のコミットログを確認して何が起こったのかを理解しましょう。

$ git checkout master
Switched to branch 'master'
...

$ git remote add upstream git://github.com/original-developer/repository.git

$ git pull upstream master
From git://github.com/original-developer/repository
 * branch            master     -> FETCH_HEAD
Updating ...
Fast-forward
...

$ git push origin master
Counting objects: ....

$ git log

pull のまえに remote add している upstream という名前は、オリジナル版の名前です。originが自分用なので、オリジナルのほうを指す名前を o で始まる名前にしないほうがいいということで、習慣的によくこの名前が使われます。

で、もし元作者の作業がmasterで進んだのに、自分の作業ブランチが遅れたmasterを元にしてるというのはダメです。なので作業の基点をやり直すためにrebaseをしておきましょう。

$ git checkout mytopic
Switched to branch 'mytopic'
...

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: ...

ここで自分の最後のコミットとフォーク元のmasterの進化の間で競合があると、rebaseプロセスが保留されてこんなメッセージで止まります。これ初めて目にすると「あわわどうしたら」ってなりますね。でも落ち着いて。

CONFLICT (content): Merge conflict in ...
Failed to merge in the changes.
Patch failed at ...

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To check out the original branch and stop rebasing run "git rebase --abort".

このときソースコード的には競合をマージしないといけない感じになっています。

<<<<<<< HEAD:...
自分が作業してる間に元の作者がやったこと
=======
次分が古いコードをもとに行ってしまった変更
>>>>>>> mine

とにかくこの競合を解決して両方の意図が反映されたコードにすること。それができるまで他のことしちゃダメ。というのも、このときローカルリポジトリは処理保留中の状態なので、下手するとせっかくの自分の作業を紛失しちゃったりするかもしれません。あわてないあわてない。

統合できたら保留されていたrebaseの処理を再開するように指示します。

$ git rebase --continue
Applying: ...

これで競合がなくなれば、ローカルリポジトリでの同期は完了。
あとはGitHubに自分の作業ブランチをpushします。

$ git push origin mytopic

と、ここでもし過去にrebaseされていない作業ブランチをpushしていると ![rejected] とか fast-forwards とかいう例のメッセージが出てうまくいきません。これ、いちどGitHubにpushしたはいいけどやっぱりまだプルリクエストする勇気がないからってそのままにしてたけど、っていうようなブランチでよく起こります。

そんなときは、リモートの自分のコミットをいちど巻き戻します。そこに、rebaseしてマージしたその上に自分のコミットを積んだものをpushします。

$ git push -f origin HEAD~1:mytopic
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:myname/repository.git
...

$ git push origin mytopic
Counting objects: ...
...
To git@github.com:myname/repository.git
   4400314..070e6f3  mytopic -> mytopic

...というここまでの操作を繰り返して、よし送るぞという決意が固まったらGitHubのフォームから熱い思いを綴ったメッセージとともにプルリクエストを。元の作者さんだって、コミットログが少々雑でもないよりあったほうがだんぜん嬉しいのがプルリクエストです。

以上、プルリクエストの作法、というよりは、勇気が出るまで自分のブランチをメンテナンスし続けるうえで、パニックにならないための自分なりの操作手順メモです。より良い作法なんかはもっと詳しい人の情報を参考にしてください。

Gitで空フォルダを管理したいときemptyを使うか.gitignoreを使うか

で、2つの流派に分かれるempty派と.gitignore派ですが、うまく使い分けるといい感じになります。

.gitignore はキャッシュやログなど、システムがその中にファイルを作るフォルダに、 empty はもしかしたらその中にソースコードを入れるかもしれないけど今のところ空っぽな場合、 と、それぞれ使い分けると好都合。

*
!.gitignore

と書いた .gitignore を作ると、その中に入っているファイルは全部無視されて、ソースコードを入れて git commit -a してもユーザへのフィードバックなしにスルーされてしまいます。また、作業者に対して、パッと見でそこが無視フォルダだからファイルを置くとき注意しないといけないと気づかれない可能性もあります。その代わり単体で意味を成すので、より上位の.gitignoreと関係しなくて済むのでシンプルです。

empty が置かれているフォルダでファイルが作られてしまうのを無視するには、より上位の.gitignoreで別途指定しないといけません。その方法を多用していると、(GUIを使っている場合はとくに)うっかりemptyファイル自体が無視されているのに気付かないことがありますし、それを登録するには、git add -f folder/empty というコマンドを打たないといけなくて作業のスムーズさがなくなります。

emptyを置いたままだというのを忘れていても、ファイルが commit -a に含まれるほうが良いですね。まあ、中身のない .gitignore を置いても同じなんですが、隠しファイルでないほうが消し忘れに気付きやすいのであえて empty にしておくのがいいと思います。そうすると、.gitignore はそれ以外全部無視で、empty は作りかけだから空っぽという意図の表現にもなりますね。

もともとここ http://tipshare.info/view/4f1917394b2122f523000000 に書いてたんですが、やたら長文になってしまったので、別途読みやすいフォーマットのとこに転記しました。

PHPでHTTPレスポンスコードを返すときPHP_SAPI分岐は要るのか

こたえは「たぶん要らないんじゃないの」です。はいおわり、というのでは何の話かわからないので、以下に詳しく書いておきます。

PHPのマニュアルで header() 関数を調べるとこんなことが書いてありました。

特殊な header コールが 2 種類あります。最初のものは、 文字列 "HTTP/" から始まる全てのヘッダ (大文字・小文字は区別されません) です。 このヘッダは、送信する HTTP ステータスコードを示すために使用されます。 例えば、存在しないファイルへのリクエストを処理するためにある PHP スクリプトを使用するよう (ErrorDocument ディレクティブにより) Apache を設定する場合、 そのスクリプトが正しいステータスコードを返すようにする必要があります。

< ?php
header("HTTP/1.0 404 Not Found");

FastCGI の場合は、404 のレスポンスは次の形式でなければなりません。

< ?php
header("Status: 404 Not Found");

http://www.php.net/manual/ja/function.header.php

Statusの方式で返すのは、FastCGIというよりCGI一般の仕様なので、旧来からのCGIにも通じる話です。で、これに従うと、ステータスコードを返す正しい処理というのは以下のようなかたちが正しいということになります。

if(preg_match('/^cgi/', PHP_SAPI)) {
    // Status: のほうを使う
}
else {
    // HTTP/ のほうを使う
}

ところが、これ指定しなくてもなんか動いてるみたいでした。で、気になって調べると、CakePHP2とSymfony2のソースにはこの分岐がありませんでした。逆に、Smartyには php_sapi_name() で分岐が書かれていました。テンプレートエンジンになんでそんなものが、というのは、304 Not Modified 対応っぽいです。そんなものはコントローラでやるわいというのはさておき...

nginxとFPMで実績がありそうなほうがCGIを意識してないのはおかしいぞというわけで、じっさいにCGIでPHPを叩くとどうなるか調べました。これがPHPの(Fast)CGIにおける真実です。

はい、結論。CGIモードのPHPは HTTP/ で指定したヘッダがあったらそれをStatusに置き換えてCGIの応答に使い、それを受けたApacheやnginxが勝手にレスポンスコードの記述に変換してくれる。プロトコルバージョンで1.0を指定しているのに1.1になってしまうのがその証拠。
まあ、試した環境が新しすぎてそういう機能があるのかなとも思うので、PHPの5.0代とか持ってる人がいたら試してもらえると嬉しいです。

そうそう、ApacheでCGIにPHPを使いたいときは、php.iniの cgi.force_redirect = 0 を忘れるとハマるのでご注意ください。