や...やっと理解できた!JavaScriptのプロトタイプチェーン

JavaScriptのプロトタイプチェーンについて理解しようとしたのだけど、prototypeとか__proto__とかごちゃごちゃになって、色んなブログを読んでもなかなか理解しきれなくて悶々としていたのだが、図を書いたらパッと理解できた!以下、情報ソースはなるべくECMAScript仕様書(3rd)を元にするようにして書きました

なぜ分かりづらいのか?

そもそも、なぜJavaScriptのプロトタイプチェーンは自分にとってこうも分かりづらかったのだろうか?自分なりに分析してみると、まず、「似ているが違う用語が沢山ある」という点がある。ざっとあげただけでも、「prototypeと__proto__」「__proto__と[[Prototype]]」「FunctionとFunctionオブジェクト」などがある。そして次に、「入り組んだ構造が動的に変化する」という点がある。上記のように似たような用語が沢山出てくる上に、それらの構造が入り組んでおり、しかもそれらが動的に変化していくのだ。これは自分の脳みそではきつい。さらに、__proto__プロパティは処理系によっては実装されていない(ChromeFirefoxならおk)など「環境依存」の部分もある。自分が中々理解できなかったのはこういった要素に原因があるのではと思った。

図にすれば理解しやすくなる!

なので、上記を解決するために「図」を用いることにした。それにより「用語が明確に区別され」「構造を状態ごとに追う事ができる」と思ったからだ。ということで、JavaScriptのオブジェクト構造がどのようになるかを図で可視化していく。図は以下のように「丸はオブジェクト。矢印はプロパティ」というシンプルな図を用いることにする。


サンプルコード

以下のサンプルコードを実行した時のオブジェクト構造を図にする

var C = function (name) {
    this.name = name;
};

C.prototype.x ='xxx';

var c1 = new C('hoge');
var c2 = new C('fuge');

C.prototype.y = 'yyy';

C.prototype = {z: 'zzz'};
var c3 = new C('piyo');

関数の定義

まずはこの部分からみていく

var C = function (name) {
    this.name = name;
};
唯一のオブジェクト「グローバルオブジェクト」

・まず、JavaScriptには唯一のオブジェクト、グローバルオブジェクトが存在する
・グローバルコンテキストで変数を定義すれば、グローバルオブジェクトのプロパティになる
・上記のコードでは、グローバルコンテキストで変数Cにfunction式で生成したオブジェクトをセットしている
・つまり、グローバルオブジェクトのプロパティCにオブジェクトをセットしていることになる

function式で生成されるオブジェクトは「Function」から生成された「Functionオブジェクト」

・では、function式で生成されるオブジェクトとはどのようなオブジェクトだろうか?
・まず前提として、ここではfunction式でオブジェクトを生成しているが、それ以外にもFunctionコンストラクタとfunction文を使ってもほぼ同様のことができる
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Statements/function
・で、function式を実行すると組み込みのFunctionというオブジェクトを元に、新たなオブジェクトが生成される
・Functionを元に生成されたオブジェクトを総称してここでは「Functionオブジェクト」と呼ぶことにする(ECMAScript仕様書(3rd )では、「Function Object」と呼ばれている)
・今回のサンプルにおいて、Cは「Functionオブジェクト」である

全ての「Functionオブジェクト」はprototypeプロパティを持つ

・では、Functionオブジェクトとはどのようなオブジェクトだろうか?
・まず、全てのFunctionオブジェクトはprototypeというプロパティを持つ
・そしてprototypeプロパティの先には通常何らかのオブジェクトがセットされている
・実はFunctionも自身から生成されたFunctionオブジェクトである
・なので、FunctionもCもprototypeプロパティをもつ

全ての「オブジェクト」は__proto__プロパティを持つ

・次にオブジェクト全般について突っ込んでみていく
・まず、全ての「オブジェクト」は内部プロパティ[[Prototype]]を持つ。これはとても重要なポイント。

Internal properties and methods are not part of the language. They are defined by this specification purely for expository purposes. (略)Native ECMAScript objects have an internal property called [[Prototype]].

ECMAScript仕様書(3rd )

・[[Prototype]]は仕様上、内部プロパティとされているが実際に__proto__というプロパティ名でプログラムから扱える実装が多い(ChromeFirefox
・ただし、__proto__が存在しない実装もあるので注意
・ここでは[[Prototype]]を__proto__という呼び名で統一することにする

Functionオブジェクト.__proto__は、Function.prototype

・__proto__プロパティの参照先を詳しくみていく
・まず、「Functionオブジェクト(ここではC)」の__proto__プロパティは、Function.prototypeを参照する

__proto__があると何が嬉しいのか?

・自分自身に存在しないプロパティ/メソッドが呼び出された時、__proto__を辿った先にあるオブジェクトにそのプロパティ/メソッドが無いか探し、あればそれを使う
・例えば、Function.prototypeにはtoStringメソッドが存在する。なので、以下のようにするとtoStringが呼び出せる
・C自身はtoStringメソッドを持っていないが、__proto__を辿った先にあるFunction.prototypeがtoStringメソッドを持っているので、それが呼び出される。

C.toString();
//"function (name) {                                                                                                                                                       
//    this.name = name;
//}"

検証:
(hasOwnPropertyメソッドは、オブジェクトが指定されたプロパティを持っているかどうかを示す真偽値を返す)

C.hasOwnProperty('toString'); //false
Function.prototype.hasOwnProperty('toString'); //true
Functionオブジェクト.__proto__.__proto__は、Object.prototype

・さきほど全てのオブジェクトは__proto__プロパティを持つと述べたが、では、Functionオブジェクトの__proto__はどこまで繋がるのだろうか?
・まず、Functionオブジェクト.__proto__.__proto__は、Object.prototypeである。実はObjectも「Functionオブジェクト(Functionから生成されたオブジェクト)」である。そのため、Objectもprototypeプロパティを持っているのだ
・さらに駆け上り、Object.prototype.__proto__の先は何かというと、それはnullになってそこで終わっている
・Object.prototypeにはhasOwnPropertyメソッドがある。なので、以下のようにするとチェーンを辿ってメソッドが実行できる

C.hasOwnProperty();

検証:
hasOwnPropertyメソッドはObject.prototypeのメソッドである

C.hasOwnProperty('hasOwnProperty'); //false
C.__proto__.hasOwnProperty('hasOwnProperty'); //false
C.__proto__.__proto__.hasOwnProperty('hasOwnProperty'); //true
ObjectとFunctionの__proto__が指す先はFunction.prototype

・全てのオブジェクトは__proto__プロパティを持つので、ObjectとFunctionも__proto__プロパティを持つはず。では、その参照先はどうなっているだろうか?
・ObjectとFunctionはいずれも__proto__プロパティがFunction.prototypeを参照している
・これはつまり、ObjectはFunctionから生成され、FunctionはFunction自身から生成されているといえる
・なので、ObjectもFunctionも、Cと同じく「Functionオブジェクト」であるといえる

検証:

Object.__proto__ === Function.prototype //true
Function.__proto__ === Function.prototype //true
C.__proto__ === Function.prototype //true
C.prototypeは空のオブジェクト

・ここで自分が生成した「C」に話を戻す。Cのprototypeはどのようなオブジェクトだろうか?
・Functionから生成したFucntionオブジェクトのprototypeは、デフォルトではnew Object()により生成されたオブジェクト(つまり空のオブジェクト)となる。なので、その空オブジェクトの__proto__プロパティはObject.prototypeを指す

検証:

C.prototype.__proto__ === Object.prototype //true
Functionオブジェクト.prototypeは、constructorプロパティを持つ

・少し脱線するが、全てのFunctionオブジェクト.prototypeはconstructorというプロパティを持つ
・なので、全てのFunctionオブジェクトはそのprototypeプロパティが参照する先のオブジェクトと相互リンクを張っている構図となる(もちろん、これは後で付け替えられるので常にこの関係が成立するわけではない)

検証:

Object.prototype.constructor === Object //true
Function.prototype.constructor === Function //true
C.prototype.constructor === C //true

prototypeの設定と新しいオブジェクトの生成

ここで冒頭のコードに戻って、以下の部分がどういう意味なのかを見る。

C.prototype.x ='xxx';

var c1 = new C('hoge');
var c2 = new C('fuge');
Functionオブジェクト.prototypeに新しいプロパティを追加する

・Functionオブジェクト.prototypeはオブジェクトなので、プロパティを追加することができる
・なのでC.prototype.x ='xxx'; で新しいプロパティxを追加することができる

Functionオブジェクトをnew演算子付きで呼び出すと、コンストラクタとなりオブジェクトを生成する

・Functionオブジェクトをnew付きで呼び出すと、コンストラクタとなって新しいオブジェクトを生成するようになる(newをつけないと通常の関数呼び出しになる)
・newによって生成されたオブジェクトは、__proto__をそれを生成したFunctionオブジェクトのprototypeに設定する
・なので、var c1 = new C('hoge');とすると、新しいオブジェクトが作成され、そのオブジェクトの__proto__がC.prototypeを参照するようになる

・さらに新しいオブジェクトをnew演算子で作成しても、同じように__proto__の参照先がC.prototypeを参照する
・なのでvar c2 = new C('fuge');とすると、c2の__proto__がC.prototypeを参照するようになる
・結果として、ここではc1とc2が両方とも自身に存在しないxを参照できるようになる

検証:

c1.x //xxx
c2.x //xxx

後からプロトタイプオブジェクトにプロパティを追加する

・では、ここでさらにC.prototypeに新しいプロパティを追加したらどうなるだろうか?冒頭のコードにおける以下の部分である。

C.prototype.y = 'yyy';

・c1もc2もC.prototypeを参照しているので、C.prototypeに追加したプロパティは既に作成したc1とc2に反映される

検証:

c1.y //yyy
c2.y //yyy

プロトタイプオブジェクトを全く新しいオブジェクトに取り替える

・では最後に、C.prototypeの参照先オブジェクトを全く新しいものに取り替えてしまったら、どうなるだろうか?
・冒頭のコードにおける最後の部分である。

C.prototype = {z: 'zzz'};
var c3 = new C('piyo');

・さきほどはC.prototypeに新しいプロパティを追加していたが、そうではなく全く新しいものに取り替える
・さらにその後var c3 = new C('piyo');で新しいオブジェクトを生成する
・すると、先に生成していたc1とc2の__proto__はそのまま残り、新しく生成したc3の__proto__だけが新しいC.prototypeを参照するようになる
・ここで注意しなければいけないのが、__proto__のconstructorプロパティである
・取り替える前は__proto__のconstructorプロパティがCを参照していたが、新しく生成したオブジェクトはただのオブジェクトであるため、c3.constructorはObjectを参照している。constructorが必ずしも自動的に張りなおされるわけではないので注意が必要である

検証:

c1.x //xxx
c2.x //xxx
c3.x //undefined

c1.z //undefined
c2.z //undefined
c3.z //zzz

c1.constructor === C //true
c2.constructor === C //true
c3.constructor === C //false

c1.constructor === Object //false
c2.constructor === Object //false
c3.constructor === Object //true

終わり

長くなったが、これで終わり!図にしたら各オブジェクトとプロパティの違いと参照関係がハッキリして、自分としてはとってもスッキリした!
JavaScriptのもう1つのチェーン、スコープチェーンについても、図にしたら理解できそうなので、今度まとめる予定ですノシ

いつも忘れる「Railsのgenerateコマンド」の備忘録

本当にいっっつも忘れる(T-T) ので、よく使うコマンドだけメモ
なお、網羅的な解説は以下のサイトがよくまとまっている!
railsコマンド(rails) - Railsドキュメント

はじめに

全てのrailsコマンドは-h (or --help)オプションでヘルプが見れる

$ rails -h
$ rails generate -h
$ rails generate scaffold -h

generateのショートカットはg

$ rails g scaffold

generateは-p, [--pretend]でドライランできる

$ rails generate scaffold AdminUser name:string mail:string -p

generateで生成したファイルを削除するにはdestory

$ rails destroy AdminUser

主要generateコマンドで生成されるファイル

デフォルトでは(オプションを与えなければ)、以下のようにファイルが生成される

コマンド名 コントローラ ビュー モデル マイグレーション アセット ルート テスト ヘルパー
scaffold
scaffold_controller × × × ×
controller × ×
model × × × × ×
migration × × × × × ×

scaffold

全部入り

$ rails generate scaffold NAME [field[:type][:index] field[:type][:index]] [options]
NAME モデル名 (単数系)。under_score記法とCamelCased記法どちらでもよい
field カラム名
type
index インデックス(uniqueかindex)
options オプション

$ rails generate scaffold AdminUser name:string mail:string

scaffold_controller

コントローラとビューをデフォルトのアクション(index〜destroyの計7つ)で生成する

$ rails generate scaffold_controller NAME [options]
NAME モデル名(単数系)。under_score記法とCamelCased記法どちらでもよい。
options オプション

$ rails generate scaffold_controller AdminUser

controller

コントローラとビューを生成する

$ rails generate controller NAME [action action] [options]
NAME コントローラ名(単数系か複数系かは用途に合わせて )。under_score記法とCamelCased記法どちらでもよい
action アクション
options オプション

$ rails generate controller AdminUsers index show

model

モデルとマイグレーションを生成する

rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
NAME モデル名 (単数系)。under_score記法とCamelCased記法どちらでもよい
field カラム名
type
index インデックス(uniqueかindex)
options オプション

$ rails generate model AdminUser name:string mail:string

migration

マイグレーションを生成する

$ rails g migration NAME [field[:type][:index] field[:type][:index]] [options]
NAME マイグレーション
field カラム名
type
index インデックス(uniqueかindex)
options オプション

$ rails generate migration AddGroupIdToAdminUsers group_id:integer
マイグレーション実行
$ rake db:migrate

終わり

他にもmailerやintegration_testなどの生成コマンドがあるが、自分はあまり使わないので、必要になったら!

GitとSubversionを併用する

仕事ではSubversion(以下、svn)を使っているのだが、これをgitに置き換えたい。
とはいえ、いきなり全体をgitに移行するのはキツそうなので、以下のようなプロセスを踏もうと思う。

(1) 自分の環境だけgitを使えるようにする(中央リポジトリsvn
(2) 他のメンバー(数名規模)もgitを使えるようにする(中央リポジトリsvn
(3) 中央リポジトリsvnからgitに変える

このエントリでは上記(1)の経緯を書きとめておく。

現状

・以下のように中央にsvnリポジトリが存在していて、各人がそこからチェックアウトしている
・各人はチェックアウトした「svn作業コピー」から「作業用ディレクトリ」にコピーしている(cpやrsyncで)
・各人の「作業用ディレクトリ」のhtdocsはdev環境としてブラウザから閲覧可能
・各人の「svn作業コピー」と「作業用ディレクトリ」は一致しない(バージョン管理しないファイルが沢山ある)
・各人の「svn作業コピー」と「作業用ディレクトリ」の差分管理がとにかく大変な状況

自分の環境だけでもgitにおきかえたい。方法として以下の3つが思いついたので、それぞれ試してみた。
最終的に「(C) git-svnを使う」ことになったのだが、折角なのでそこまでの経緯を記録しておく。

(A) 「svn作業コピー」を「gitリモートリポジトリ」として使う

以下のようにsvn作業コピーをgitリモートリポジトリ(bareではないワーキングツリーを持つリポジトリ)にして、gitローカルリポジトリからpull/pushする形を考えた。

結果: 不採用
理由: gitのリモートリポジトリは基本的にbareリポジトリであることが推奨されるため。


# svnリポジトリからチェックアウトしてsvn作業コピーを作る
$ svn checkout [svnリポジトリ] [svn作業コピー]

# svn作業コピーをgit管理下におく(gitリモートリポジトリを兼任させる)
$ cd [svn作業コピーのトップ]
$ git init

# svnの管理下から.gitと.gitignoreを除去
# ※設定用のエディタにはvimを使用した
# ※参考: http://log.xinu.jp/2011-07-25-1.html
$ export svn_EDITOR="vim"
$ svn propedit svn:ignore ./
.git
.gitignore

# 確認
$ svn status
$ svn status --no-ignore

# gitの管理下から.svnを除去
$ vim .gitignore
.svn

# 確認
$ git status

# gitにコミットする
$ git add .
$ git commit -m 'init'

# 作業ディレクトリ(gitローカルリポジトリ)に移動し
# svn作業コピー(gitリモートリポジトリ)からcloneする
$ cd [作業ディレクトリ|gitローカルリポジトリ]
$ git clone [svn作業コピー|gitリモートリポジトリ]

# なんらかのファイルに変更を加えた後、pushする
$ git add .
$ git commit -m 'test'
$ git push

# エラーが出る!
(略)
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error: 
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error: 
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
(略)

(B) 「svn作業コピー」を「gitローカルリポジトリ」として使う

以下のようにsvn作業コピーをgitローカルリポジトリとして使う構成を考えた。
gitリモートリポジトリを別途用意して、それを中間地点としてpull/pushするような構成。

結果: 不採用
理由: 一応できたが、何度もpushやpullをする必要があって面倒。また複雑なのでミスも多くなりそう。


# >>> ここから
$ svn checkout file:hoge fuge
$ cd [svn作業コピーのトップ]
$ git init
$ export svn_EDITOR="vim"
$ svn propedit svn:ignore ./
.git
.gitignore
$ vim .gitignore
.svn
$ git add .
$ git commit -m 'init'
# <<< ここまでは1.と同じ

# gitのbareリポジトリを作る
$ mkdir [git_bareリポジトリの]
$ cd [git_bareリポジトリ]
$ git clone --bare [svn作業コピー]

# svn作業コピーに移動し、作成したgitのbareリポジトリをgitリモートリポジトリに設定する
$ cd [svn作業コピー]
$ git remote add [リモートリポジトリ名] [git_bareリポジトリ]

# 作業用のディレクトリにcloneする
$ cd [作業用ディレクトリ]
$ git clone [git_bareリポジトリ]

# 作業用ディレクトリでなんらかのファイルに変更を加えた後、pushする
$ git add .
$ git commit -m 'test'
$ git push

# svn作業コピーでgit pull
$ cd [svn作業コピー]
$ git pull

# svnの状態を確認
$ svn status -u

(C) git-svnを使う

結果として定石通りにこれを採用した。

自分の環境での問題は、git管理下におきたくないファイルが異常に沢山あること。
そこで.gitignoreファイルをかなり丁寧に指定した。
.gitignoreの指定方法にについてはこちらのエントリにまとめた。

また、以下のサイトを参考にして作業した。
git svn cloneをやるときは--prefix svn/をつけるべき - DQNEO起業日記
Git - Git と Subversion

# 現在の作業用ディレクトリをいったん退避
$ mv [作業用ディレクトリ] [旧作業ディレクトリ]

# 新しい作業用ディレクトリを作成し、git管理下に
$ mkdir [新作業用ディレクトリ]
$ cd [新作業用ディレクトリ]
$ git init

# git-svnがインストールされているか確認。なければインストール
$ git svn --version

# subversionのリポジトリを取り込む
# <注意!>過去のコミット数が多いと、死ぬほど時間がかかる場合がある(数日かかる場合もあるらしい)ので注意
# 自分の環境では、1000コミットくらいあるリポジトリが15分くらいだった
$ git svn clone -s --prefix svn/ [svnリポジトリ(trunkはつけない)] [clone後ディレクトリ名]

# svnリポジトリの情報確認
# svnリポジトリのログ確認
$ git svn info
$ git svn log

# .gitignoreを細かく指定
$ vim .gitignore
色々な設定

# 旧作業用ディレクトリを新作業用ディレクトリに上書き
$ cp -R [旧作業用ディレクトリの内容] [新作業用ディレクトリ]

# 確認
git status

実運用

こちらのブログに掲載されている内容が大変参考になりました。
git-svnを使うときのベストプラクティス - Life goes on
git-svnの使い方を覚えた - idesaku blog

今後

各人がローカル環境でgitに慣れたらリモートリポジトリ自体をsvnからgitに移管しよう

gitignoreまとめ

Gitで無視ファイルを細かく設定する際にはまったので、メモ

ヘルプ

こまったら、これで

$ man gitignore

web版

前提

まずは前提を抑えておかないと、はまる

既にトラックされたファイルはgitignoreが効かない
$ git init
$ touch hoge.txt
$ git add hoge.txt # トラックされた後に
$ vim .gitignore # 無視設定しても 
hoge.txt
$ git status # 効かない

既にトラックされたファイルを無視対象にしたければ、git rm --cached

$ git rm --cached hoge.txt # 上記のトラックされたファイルをインデックスから削除すれば(ワークツリーはそのまま)
$ git status #gitignoreが効く

# もしhoge.txtをcommit済みの場合
$ git commit -m 'delete hoge.txt' # コミットしてリポジトリからも消す
空のディレクトリは管理されない
$ git init
$ mkdir aaa # 空のディレクトリを作成
$ git add . 
$ git status # git管理下にならない

Git管理下にしたければ空ファイルを置けばよい。名前は何でもよいが.gitkeepとするのが慣習

$ touch aaa/.gitkeep # 空のディレクトリに.gitkeepという名前の空ファイルを作成
$ git add . 
$ git status # git管理下になる

.gitignore vs .gitkeep
http://stackoverflow.com/questions/7229885/gitignore-vs-gitkeep

優先順位

gitignoreは以下4つの方法で設定できて、それぞれ優先順位がある

↑優先度「高」

(1) コマンドラインからの指定

・git ls-filesなどのコマンドがコマンドラインから無視パターンを指定できる。

<例 >
untrackedなファイルから*.txtにマッチするファイルを除いて表示

$ git ls-files -o --exclude-standard -x '*.txt'
(2) .gitignoreファイル

・プロジェクトのメンバー全員で共通して無視したいパターンに使う。
・通常、バージョンコントロール内に入れてプロジェクトメンバー間でシェアする。
複数の.gitignoreファイルがある場合、階層が深い方が優先される

<例>
.gitignore

*.txt

100/.gitignore

!*.txt

結果

.
├── .gitignore
├── 100/
│   ├── .gitignore
│   └── hoge.txt # 無視しない(100/.gitignoreが適用)
├── 200/
│   └── hoge.txt # 無視する
└── hoge.txt # 無視する
(3) $GIT_DIR/info/excludeファイル

・自分だけの設定。自分がこのプロジェクトにおいてだけ無視したいパターンに使う。
・例えば、自分だけがこのプロジェクトで使っているデバッグ用の俺俺スクリプトなどに。
・$GIT_DIRは、特に設定していなければワークツリーのトップにある.gitディレクトリを指す

<例>

# このプロジェクトに特有な自分だけのスクリプトmy_script.rbをバージョン管理外に
$ vim .git/info/exclude
# git ls-files --others --exclude-from=.git/info/exclude                                                                                                                          
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
my_script.rb
(4) core.excludesfile(設定用変数)で指定されたファイル

・自分だけの設定。自分がどのようなプロジェクトであっても常に無視したいパターンに使う。
・例えば、vimの.swpやmacの.DS_Storeなど

<例>

# git configコマンドで設定(ファイル名は.gitignoreでなくてもよい)
$ git config --global core.excludesfile ~/.gitignore

# すると~/.gitconfigに以下のような記載が追加される
[core]
  excludesfile = /Users/hidenorimaehara/.gitignore

# 対象ファイルに無視パターンを設定
$ vim ~/.gitignore
.DS_Store

↓優先度「低」

無視パターンの書き方

無視パターンの書き方は以下。適用される階層に注意

どこにも/なし

・shell glob pattern(※1)として扱う
・.gitignoreファイルにおいては、その.gitignoreファイルを基準とした相対パスでパターン解析
・つまり、同階層以下全てに適用される(上位階層には適用されない)。
・.gitignoreファイル以外(※2)では、ワークツリーのトップレベル(※3)を基準とした相対パスでパターン解析

<例>
100/.gitignore

*.txt

結果

.
├── 100/
│   ├── .gitignore
│   ├── 110/
│   │   └── hoge.txt #無視する
│   └── hoge.txt #無視する
└── hoge.txt #無視しない(100/.gitignoreの上位階層のため)

※1 http://hgbook.red-bean.com/read/file-names-and-pattern-matching.html#id381291
※2 $GIT_DIR/info/excludeファイルなど
※3 git rev-parse --show-toplevelで確認できる

末尾に/

ディレクトリだけに適用される(ファイルには適用されない)

<例>
100/.gitignore

[0-9][0-9][0-9]/

結果

├── 100/
│   ├── .gitignore
│   ├── 110/ #無視する
│   │   └── hoge.txt
│   ├── 120 #無視しない(120はファイルであり、ディレクトリでないため)
│   └── hoge.txt
└── hoge.txt
先頭に/

・その.gitignoreファイルと同階層にだけ適用される
・「/」はその.gitignoreファイルを基点としたパスのことであり、ファイルシステムのルートではない

<例>
100/.gitignore

/hoge.txt

結果

├── 100/
│   ├── .gitignore
│   ├── 110/
│   │   └── hoge.txt #無視しない(100/.gitignoreの直下ではないため)
│   └── hoge.txt #無視する
└── hoge.txt #無視しない
途中に/

ワイルドカード(*など)が「/」にマッチしないshell glob patternとして解析する
・これは例を見た方がはやい

<例>
100/.gitignore

*/hoge.txt 

結果

├── 100/
│   ├── .gitignore
│   ├── 110/
│   │   ├── 111/
│   │   │   └── hoge.txt #無視しない(100/.gitignoreを基準とした*/hoge.txtに合致しないため)
│   │   └── hoge.txt #無視する
│   └── hoge.txt #無視しない(100/.gitignoreを基準とした*/hoge.txtに合致しないため)
└── hoge.txt #無視しない
!

・逆の意味になる(無視しない)
・これを用いることで、ホワイトリスト方式が実現できる

<例>
.gitignore

# まず直下のファイルとディレクトリを全て無視する                                                                                                                                 
/*

# ただし、以下は無視しない
!/.gitignore
!/100

結果

├── .gitignore #無視しない
├── 100/ #無視しない
│   └── hoge.txt
├── 200/ #無視する
│   └── hoge.txt
├── fuge.txt #無視する
└── hoge.txt #無視する

終わり

Githubには各フレームワークや環境におけるデフォルトのgitignoreが集まっており便利
https://github.com/github/gitignore

PHPを愛する試み 〜self:: parent:: static:: および遅延静的束縛〜

PHPを愛する試みというものを個人的にやっている

PHPを愛する試み
PHPを愛する試み 〜調教編〜

今回は、self:: parent:: static:: 遅延静的束縛について図で整理してみた。

スコープ定義演算子 (::)

まず「::」について。これはスコープ定義演算子という。マニュアルには以下のようにある。

スコープ定義演算子 (::)

スコープ定義演算子 (またの名を Paamayim Nekudotayim)、 平たく言うと「ダブルコロン」は、トークンのひとつです。 static, 定数 およびオーバーライドされたクラスのプロパティやメソッドにアクセスすることができます。これらの要素をクラス定義の外から参照する際には、 クラスの名前を使用してください。PHP 5.3.0 以降では、変数を用いてクラスを参照することも可能です。 変数の値に (self や parent、 static といった) キーワードを指定することはできません。
...
http://www.php.net/manual/ja/language.oop5.paamayim-nekudotayim.php

分かりづらいので、自分流に解釈する。例えば、以下の図ように沢山のfoo()があるとして、どのfoo()なのかを決定するための演算子が::である。「::の右側にあるメソッドやプロパティが、::の左側のスコープに属すると決定する演算子」と考えればしっくりくる。

self::やparent::というのは、このスコープを表す方法の1つ。全部で以下の4つがある。

方法 スコープ 使える場所
クラス名:: 明示したクラス クラス定義の外or中
self:: self::が記載されたクラス クラス定義の中
parent:: parent::が記載されたクラスの親クラス クラス定義の中
static:: 直近の "非転送コール" のクラス クラス定義の中

1つずつみていく

クラス名::

クラス名::とすると、スコープは「明示したクラス」となる。
クラス定義の外でも中でも使える。

<?php
class C
{
  public static function foo() {
    D::foo(); //foo()のスコープはD
  }
}

class D
{
  public static function foo() {
    echo 'D_foo' . PHP_EOL;
  }
}

C::foo(); //foo()のスコープはC。結果:D_foo 


self::

self::とすると、スコープは「self::が記載されたクラス」となる。
クラス定義の中でしか使えない。

<?php

class C
{
  public static function foo () {
    self::bar(); //bar()のスコープはself::が記載されたクラス(C)
  }
                                                                                                                                  
  public static function bar () {
    echo 'C_foo' . PHP_EOL;
  }
}

class CC extends C
{
  public static function bar () {
    echo 'CC_foo' . PHP_EOL;
  }
}

C::foo(); //C_foo
CC::foo(); //C_foo


parent::

parent::とすると、スコープは「parent::が記載されたクラスの親クラス」となる。
クラス定義の中でしか使えない。

<?php                                                                                                                             

class C
{
  public static function bar () {
    echo 'C_bar' . PHP_EOL;
  }
}

class CC extends C
{
  public static function foo () {
    parent::bar(); //bar()のスコープはparent::が記載されたクラス(CC)の親クラス(C)
  }

  public static function bar () {
    echo 'CC_bar' . PHP_EOL;
  }  
}

CC::foo(); //C_bar


static::

これが本題。static::とすると、スコープは「直近の "非転送コール" のクラス」となる。
クラス定義の中でしか使えない。

ややこしいのが「直近の "非転送コール" のクラス」というもの。マニュアルにはこう書いてある。

遅延静的束縛 (Late Static Bindings)
...
より正確に言うと、遅延静的束縛は直近の "非転送コール" のクラス名を保存します。 静的メソッドの場合、これは明示的に指定されたクラス (通常は :: 演算子の左側に書かれたもの) となります。静的メソッド以外の場合は、そのオブジェクトのクラスとなります。 "転送コール" とは、self:: や parent::、static:: による静的なコール、 あるいはクラス階層の中での forward_static_call() によるコールのことです。 get_called_class() 関数を使うとコール元のクラス名を文字列で取得できます。 static:: はこのクラスのスコープとなります。
...
http://php.net/manual/ja/language.oop5.late-static-bindings.php

整理すると、

非転送コール: C::foo()や$c->foo()といったクラス名(もしくはオブジェクト)を明示した呼び出し
転送コール: self:: parent:: static::もしくはクラス階層の中での forward_static_call() での呼び出し
遅延静的束縛 非転送コール時に、明示されたクラス名(もしくはオブジェクトのクラス名)を保持する機能

となる。
これらをふまえて、static::を使ったコードを書いてみる

<?php
class C
{
  public static function foo() {
    static::bar(); // bar()のスコープは直近の "非転送コール" のクラス(C::foo()ならC。CC::foo()ならCC)
  }

  public static function bar() {
    echo 'C_bar' . PHP_EOL;
  }
}

class CC extends C
{
  public static function bar() {
    echo 'CC_bar' . PHP_EOL;
  }
}

C::foo(); //C_bar
CC::foo(); //CC_bar       

C::foo()と呼び出した時とCC::foo()で呼び出した時の動きが変わった!
絵にすると以下のようになる


「直近の非転送コール」とは? 〜例1〜

「直近の非転送コール」が何を表すのか、もう少し詳しくみてみる。
例えば、以下のように途中で転送コール(self::など)をはさんだ場合はどうなるか

<?php
class C
{
  public static function foo() {
    self::hoge(); //self::は転送コール
  }

  public static function hoge() {
    static::bar(); // bar()のスコープは直近の "非転送コール" のクラス(この場合CC)
  }

  public static function bar() {
    echo 'C_bar' . PHP_EOL;
  }
}

class CC extends C
{
  public static function bar() {
    echo 'CC_bar' . PHP_EOL;
  }
}

CC::foo(); //CC_bar  

転送コールは無視して直近の非転送コールまで遡っていることがわかる
絵にするとこうなる


「直近の非転送コール」とは? 〜例2〜

非転送コールが複数存在する場合はどうか

<?php
class C
{
  public static function foo() {
    C::hoge(); //非転送コール
  }

  public static function hoge() {
    static::bar(); // bar()のスコープは直近の "非転送コール" のクラス(この場合、C)
  }

  public static function bar() {                                                                                                                                                                            
    echo 'C_bar' . PHP_EOL;
  }
}

class CC extends C
{
  public static function bar() {
    echo 'CC_bar' . PHP_EOL;
  }
}

CC::foo(); //C_bar

非転送コールが複数存在する場合は、直近の非転送コールに解決されている
絵にするとこうなる

いずれにおいてもstatic::からコールスタックを遡って、一番近い非転送コール(転送コールは除く)で明示されたクラスに解決されていることがわかる。

静的メソッドの呼び出しではない場合

これまで静的メソッドの呼び出しを例としてきたが、さきほど引用したマニュアルにあるように、「静的メソッド以外の場合は、そのオブジェクトのクラス」となる

<?php                                                                                                                        
class C
{
  public function foo() {
    static::bar();
  }

  public static function bar() {
    echo 'C_bar' . PHP_EOL;
  }
}

class CC extends C
{
  public static function bar() {
    echo 'CC_bar' . PHP_EOL;
  }
}

$cc = new CC();
$cc->foo(); //CC_bar

静的メソッドでなくても直近の非転送コールまで遡って行く仕組みは同じだ。
これを絵にするとこうなる

注意:「メソッド・プロパティ定義のstatic」「静的変数static」とは違う

static::について混乱するのは、staticが遅延静的束縛以外の意味でも使われるからだ。マニュアルには以下のように書いてある

static キーワード

このページでは、static キーワードを使って静的なメソッドやプロパティを定義する方法を説明します。 static は、 静的な変数の定義 や 静的遅延束縛 にも使えます。これらの場合の static の使い方は、 それぞれのページを参照ください。
http://php.net/manual/ja/language.oop5.static.php

つまり、staticはこれまで見てきたように遅延静的束縛以外に、以下2つの意味で使われる。

・静的なメソッド・プロパティを定義するためのstatic

<?php
class C
{
  public static $v = 'hoge';                                                                                                 

  public static function foo() {
    echo 'foo' . PHP_EOL;
  }
}

var_dump(C::$v); //hoge
C::foo(); //foo

・ 静的変数のstatic

<?php
class C
{
  public function countup() {
    static $count; //静的変数
    $count++;
    var_dump($count); 
  }
}

$c = new C();
$c->countup(); //1
$c->countup(); //2
$c->countup(); //3 

同じキーワードが色々な意味で使われるところがややこしい。。。

以上

長かったがstaticについては大分整理できた。

余談

self::は__CLASS__。static::はget_called_class()で参照できる
<?php
class C
{
  public static function foo() {
    var_dump(__CLASS__); //常にC
    self::bar(); //常にC::foo()

    var_dump(get_called_class()); //CかCC
    static::bar(); //C::foo()かCC::foo()
  }

  public static function bar() {
    echo 'C_bar' . PHP_EOL;                                                                                                  
  }
}

class CC extends C
{
  public static function bar() {
    echo 'CC_bar' . PHP_EOL;
  }
}

C::foo();
CC::foo();
new self() new parent() new static()

self, parent, staticキーワードはオブジェクト生成にも使える。

<?php
class C
{
  public static function foo() {
    var_dump(new self());
  }

  public static function hoge() {
    var_dump(new static());
  }
}

class CC extends C
{
  public static function bar() {
    var_dump(new parent());
  }
}

C::foo(); //object(C)
CC::bar(); //object(C)                                                                                                            
C::hoge(); //object(C)
CC::hoge(); //object(CC)

終わり

絵にして整理したら理解が進んだ!
フレームワークソースコードを読んでいるとstatic::やself::はガンガン出てくるので自分の中ではよい整理になった!

PHPを愛する試み 〜調教編〜

PHPを愛する試みというのを個人的にやっている。

最近仕事でPHPをガリガリ使わなければならない状況になってしまった。そのため可及的速やかにPHPを愛する必要がある。

tap

ときに、Rubyにはtapという素敵なメソッドがある。

tapかわいいよtap

PHPでも$obj->tap(...)みたいに書きたい!

p

ときに、Rubyにはpという素敵なメソッドがある。

p (Kernel)

PHPでも$obj->p()みたいに書きたい!

ということで

今回はPHPを無理矢理Rubyっぽく使うという調教プレイでたわむれることにした。

どうやるか

ざっと調べたところ、

・どんなオブジェクトでもtapとpを使いたい。
・けどPHPにはRubyのように基底クラスを簡単に書き換えるような仕組みはなさそう
・かといって全てのクラスにメソッド実装するのはダルイ
C言語で拡張とか僕には無理だお

という感じだった。しかし、さらに調べるとrubyactivesupportっぽいことをPHPでやってる洋モノの変態さんを発見した。

ActiveSupport for PHP - Ruby style

なんかよさそう。ということで、ここから発想を得て以下のような仕組みを考えてみた。

・任意のオブジェクト$objをruby()に食わせると、->tap()と->p()が使えるようになる
・->tap()と->p()はメソッドチェインで->tap()->p()のように繋げることができる
ruby()に食わせたオブジェクトは->php()で元のオブジェクトに戻せる

Rubyっぽいメソッドを叩き込むドSファイル

ruby.php

<?php
class Ruby
{
    public function __construct($origin) {
        $this->origin = $origin;
    }

    public function tap($callback) {
        $callback($this->origin);
        return $this;
    }

    public function p() {
        var_dump($this->origin);
        return $this;
    }

    public function php() {
        return $this->origin;
    }
}

function ruby($origin) {
    return new Ruby($origin);
}

使ってみる

rubynize.php

<?php                                                                                                                                                             
require 'ruby.php';

class C
{
    public function __construct() {
        $this->name = 'hoge';
    }   
}

// オブジェクトをvar_dump
$c = new C();
ruby($c)->p();

// オブジェクトのnameプロパティにfugeをセット
$c = new C();
ruby($c)->tap(function ($x) {$x->name = 'fuge';});                                                                                                            
var_dump($c);

// オブジェクトのnameプロパティにfugeをセットし、オブジェクトをvar_dump
$c = new C();
ruby($c)->tap(function ($x) {$x->name = 'fuge';})->p();

// 上記の返り値をRubynizeする前のものにする
$c = new C();
$new_c = ruby($c)->tap(function ($x) {$x->name = 'fuge';})->p()->php();
var_dump($new_c);

チェイン(鎖)ー!

さらに使ってみる

rubynize2.php

<?php
require 'ruby.php';
 
class C
{
  public function setName($name) {
    $this->name = $name;
    return $this;
  }
 
  public function setSex($sex) {
    $this->sex = $sex;
    return $this;
  }
}
 
 
// 普通のメソッドチェイン
$c = new C();
$c->setName('hoge')->setSex('man');
var_dump($c);
 
// tapでnameをhoge->fugeに
$c = new C();
ruby($c->setName('hoge'))
  ->tap(function ($x) {$x->name = 'fuge';})
  ->php()
  ->setSex('man');
var_dump($c);
 
// tapでnameをhoge->fugeに。sexをman->womanに。さらにチェーンでvar_dump
$c = new C();
ruby(
  ruby(
    ruby($c->setName('hoge'))
    ->tap(function ($x) {$x->name = 'fuge';})
    ->php()
    ->setSex('man')
  )   
  ->tap(function ($x) {$x->sex = 'woman';})
)->p();

チェインチェイン(鎖鎖)ー!

終わり

ruby()が必要だったり->php()しなきゃだったりで、もっとすっきり出来そう。
だけど自分の能力ではこの辺が限界だお...
もっとPHPのことを知らなきゃですな...

※ 個人的にPHPとたわむれる試みなので、実用性とか(ry

※シリーズもので頑張ってる
PHPを愛する試み
PHPを愛する試み 〜調教編〜
PHPを愛する試み 〜self:: parent:: static:: および遅延静的束縛〜

unshift, shift, pop, pushが混乱するので、絵で整理した

配列の追加・取り出しに関する4つのメソッド(unshift, shift, pop, push)がいつも混乱するので絵で整理した。

絵にすれば覚えられそう!いちお練習のため、自分がよく使うRuby, PHP, JavaScriptでコードも書いた。

・いずれの言語においても上記4つのメソッドは似たような名前で存在しており、用途も同じ。
・いずれの言語のいずれのメソッドも破壊的メソッド
・言語、バージョンによってメソッドの返り値は異なる。

という具合だった。

Ruby

ruby 1.9.2

# unshift
a = [1, 2, 3]
b = a.unshift 0
p a #[0, 1, 2, 3]
p b #[0, 1, 2, 3]

# shift
a = [1, 2, 3]
b = a.shift
p a #[2, 3]
p b #1

# pop
a = [1, 2, 3]
b = a.pop
p a #[1, 2]
p b #3

# push
a = [1, 2, 3]
b = a.push 4
p a #[1, 2, 3, 4]
p b #[1, 2, 3, 4]

# <<
a = [1, 2, 3]
b = a << 4
p a #[1, 2, 3, 4]
p b #[1, 2, 3, 4]

PHP

PHP 5.3.10

<?php
// unshift
$a = array(1, 2, 3);
$b = array_unshift($a, 0);
var_dump($a); //array(0, 1, 2, 3)                                                                                                                             
var_dump($b); //4(結果配列の要素数)

// shift
$a = array(1, 2, 3);
$b = array_shift($a);
var_dump($a); //array(2, 3)
var_dump($b); //1

// pop
$a = array(1, 2, 3);
$b = array_pop($a);
var_dump($a); // array(1, 2)
var_dump($b); // 3

// push
$a = array(1, 2, 3); 
$b = array_push($a, 4); 
var_dump($a); //array(1, 2, 3, 4)
var_dump($b); //4 (結果配列の要素数)

// [] =
$a = array(1, 2, 3);
$b = $a[] = 4;
var_dump($a); //array(1, 2, 3, 4)
var_dump($b); //4(追加した値)

JavaScript

node 0.6.12

var a = [1, 2, 3];                                                                                                                                                                                          
var b = a.unshift(0);
console.log(a); //[0, 1, 2, 3]
console.log(b); //4(結果配列の要素数)

var a = [1, 2, 3];
var b = a.shift();
console.log(a); //[2, 3]
console.log(b); //1

var a = [1, 2, 3];
var b = a.pop();
console.log(a); //[1, 2]
console.log(b); //3

var a = [1, 2, 3];
var b = a.push(4);
console.log(a); //[1, 2, 3, 4]
console.log(b); //4(結果配列の要素数)