PHPしかできない奴はいつまで経ってもPHPしかできない?
PHPには下記のような都市伝説があります。
他言語から学んだ者がPHPを習得することは容易いが、PHPから学んだ者が他言語を習得することは難しい。PHPから始めた者は、一生PHPのみで過ごすか、一度PHPを忘れて、初心から他言語を学ぶしかない。
まぁ、言っているのは私一人ですけどね。引用っぽくしてますが、引用元はありません。都市伝説ですので。1
これはとあるRubyの開発者がPHPをdisったことから始まる第2回PHPなめんな選手権2のところで、404 Blog Not Found:「PHPなめんな」と「(Perl|Python|Ruby)をなめんな」の違いにあった、次の言葉がきっかけです。
それは、PHPユーザーは他の言語から学んでいるのか、という点。
PHPにも素晴らしいアプリケーションはありますし、それらを作成している人達もいます。ただ、その人達は他言語を知っていて、PHPの利点・欠点を理解した上で、あえてPHPを使っていると言われています。しかし、PHPのdisに対してちぐはぐな反論が目立ったためでしょうか「PHPerは他言語を知らないのでは」と言われてしまいました。
では、実際はどうなのかというとよくわかりません。ですが、「PHPしかできない人が多い」と言われているのは事実なようです。人が多いからPythonやRubyよりもPHPは求人として集めやすいというのも、求人時の平均給料差を見れば納得できる物だと思います。
通常、プログラミングというのは共通部分が多く、ある言語を学んだ人は他言語を学ぶのにそれほど苦労しないと言われています。もし、それが事実であれば、PHPしかできない人というのは少ないはずです。他言語をすぐに学べると言うことは、既に学んでいてもおかしくないですし、たとえ今PHPだけしかできなくても、他言語を学ばせて即戦力にすることも容易いはずです。しかし、「PHPだけしかできない人が多い」というのが事実であれば、PHPについては他言語の学習に役に立たないため、PHPから拡げられないのではと考えられます。
もちろん、命令型か関数型か、非オブジェクト指向かオブジェクト指向か、クラスベースかプロパティベースか、などといったパラダイムが違う場合は学習し直しの部分は多くあるでしょう。しかし、PHPにはパラダイムとは違う、「他言語学習を妨げるような何か」があるのではと思っています。今回はそれを考察したいと思います。
始めに特化した物を覚えると汎化された物が覚えにくい?
人は一番初めに学んだことが一番強く印象に残ります。その後、多くのことを覚えて少しずつ学習し、修正していきますが、最初の印象を覆すには長い時間がかかります。「AはBである」という事を始めに覚えた後に「AはCである」という別の何かを言われても理解する(納得する)のは難しいと言うことです。
理解し難いと言うことですが、これには二つのパターンによってその難さが変わります。それは、その違いについて、性質が追加された物か削除された物かと言うことです。「XにおいてAはBかつCである」と始めに覚えたとします。ここで「YにおいてAはBかつCかつDである」と追加になった場合、「Yの場合のみAはDでもある」と覚えられます。しかし、「ZにおいてAはBである」と削除されていた場合は「Zの場合のみAはCではない」となるでしょうか?そうなるとAとは一体何であるかがわからなくなります。本当は「AはBである」「Xの場合のみAはCでもある」とすべきです。しかし、はじめの「XにおいてAはBかつCである」であるの印象が強すぎて「AはBかつCである」が全てにおいて成り立つと考えてしまいがちです。これを修正するには長い期間の繰り返しの学習が必要になります。
ここでPHPにもどります。つまり、PHPの「他言語学習を妨げるような何か」とは、
- 多くの他言語とは異なる特徴がある。
- 多くの他言語には無い特徴がある。
ではないかと思います。始めにPHPを学ぶことで、他言語では通用しない事を当たり前だと思ってしまい、それが他言語学習で邪魔をして、習得効率を下げていると考えられるのです。この違いの部分、それはどんな物があたるのかを見ていきたいと思います。
他言語が持っていないPHPの特徴
影響度は「その機能が当たり前だと思うことによってどれぐらい他言語の学習の妨げになるのか」を独断と偏見で5段階(低★☆☆☆☆から高★★★★★)でつけたものです。
文法
PHPタグによるテンプレート
影響度: ★★★☆☆
PHPはテンプレートとしても使用できます。逆に言うと必ずテンプレートになってしまいます。なぜなら<?php ?>
のタグに囲まれていない所はそのまま出力されてしまうからです。そのため、出力の必要としないライブラリのようなソースコードでも<?php
を先頭に書く必要があります。
そのような特徴を持つ言語はありません。JSP、ASP、ERBなど別途テンプレートを使える言語は存在しますが、言語そのものがテンプレート言語であるのはPHPだけです。
毎回<?php
を先頭に書くことは余り問題ではありません。始めにお約束のように何かを書く言語は少なくないからです。問題はテンプレートとしての使い方に慣れてしまった場合です。
PHPでも標準出力に文字列を表示するにはecho
を使えばできますが、テンプレートを使えばもっと簡単にできる場合があります。HTMLを組み合わせた場合、その方が簡易になる場合もあります。しかし、他の言語ではそのような機能はありません3。テンプレートに慣れてしまいすぎると、テンプレートがないときの書き方ができません。そうなるとどう書いて良いのかわからないとなり、その言語の学習を諦めてしまいます。
なお、テンプレートとして使うべきかそうでないかはまた別の話ですので、ここでは追求しません。
制御構造に関する別の構文
影響度: ★☆☆☆☆
PHPではif、while、for、foreach、switchに対して別の構文を用意しています。これはテンプレートとして使用したときに{}
のブロックがわかりにくくなるのを防ぐために用意されたようです。PHPはCライクと言われる続く伝統的な{}
ブロックと;
行末を使った書き方ですが、この別の構文だけは異なる書き方になっています。
他のCライクな文法の言語ではそのような物は用意されていません。あまりないと思いますが、別の構文をテンプレート以外でも積極的に使用していると、せっかくのCライクな言語であっても戸惑うことになってしまう可能性があります。
未定義の定数
影響度: ★☆☆☆☆
PHPでは未定義の定数を呼び出すとその文字列そのものになります。これはかつて便利であったため、古いコードではそのまま残っています。変数の名前がその値そのものになるというのは珍しく、Erlang等にあるアトムぐらいです。しかし、これは定数というよりシンボルに近い機能であり、未定義の時のみその文字列になるというのは極めて奇妙な仕様になっています。
E_NOTICEが発生しますので、そのような使い方をする人は少ないでしょうし、使っていないと信じたいところです。
参照渡し
影響度: ★★★☆☆
PHPには参照渡しがあります。しかし、参照渡しがある言語は少ないです。それは、参照渡しを必要としないよう言語が設計されているからです。PHPでは参照渡しが必要になるときがありますが、他の言語では参照渡し無しで実装しなければなりません。参照渡しに依存するような書き方をしてしまうと、参照渡しがない言語では戸惑ってしまう可能性があります。
幸か不幸か、参照渡しというのは(その理解が)大変難しい機能と考えられるため、PHPに深くはまり込む前であれば、使い方がわからないという可能性はあるかも知れません。
文字列結合演算子.
影響度: ★★★☆☆
文字列結合には.
演算子を使います。これはPerlと同じであり、Perlから引き継いだものと思われます。1 + 2
と1 . 2
が区別して行えることは有用かも知れません。
しかし、他言語のほとんどは.
をメンバー(プロパティ、フィールド、メソッド等)へのアクセスに使っています。PHPはメンバーへのアクセスに->
を使わざるを得ませんでした。これもPerlと同じです。C++でも->
は使いますが、ポインタへの.
ということの省略形にすぎません。
本格的なオブジェクト指向においてメンバーアクセス演算子は最も使う機能です。->
というものに慣れてしまうと.
では違和感を覚えると言うこともあり得るかも知れません。
配列(array)
array()
という書き方
影響度: ★☆☆☆☆
PHP 5.3まで配列のリテラルはarray()
を使用する方法がありました。array()
を使った書き方はまるで関数のようだですが、関数ではありません。しかし、配列のような、よく使うリテラルを関数のように書く必要があったと言うことは、PHP特有のものと言ってもいいでしょう。
なお、PHP 5.4以降は、[]
を使えるようになったため、積極的に[]
を使っていれば、影響は少ないと思われます。
配列は特殊な連想配列を兼ねる
影響度: ★★★★☆
通常、配列と言えば、順番に並んだなんらかの値の列であり、インデックスと言われる整数添字でアクセスします。それとは別に、任意の文字列等でアクセスする連想配列(mapとも言われる)と言う物があります。
PHPの配列はこの連想配列を兼ねています。しかし、この連想配列は特殊です。
- 整数添字配列と連想配列は混合しており、区別されません。
- キーは整数と文字列しか指定できません。オブジェクトはキーに使えません。
- キーに対してキャストが発生します。
"8"
という文字列をキーにすることはできず、自動的に8
という整数に変換されます。true、false、Nullもキャストが発生し、それぞれ、1, 0, ""になります。
一見JavaScriptのObjectに近いようですが、JavaScriptのObjectは全て文字列として扱いますので似て非なるものがあります。それにJavaScriptはArrayという配列とMapという連想配列(ES6以降)が別途用意されています。
配列を連想配列のように扱えると言うことは、配列を操作する関数も共通して使えますし、一見便利に見えます。しかし、そのような処理になれてしまうと、他言語のように、別々にわかれている場合はどうすれば良いのかがわからなくなります。
配列は値型で値渡し
影響度: ★★★★★
<?php
$a = [2, 3, 5];
$b = $a;
$b[0] = 42;
echo $a[0], "\n";
PHPerの皆さんであればわかっているとおり、上のコードは2
を表示します。しかし、この動作は他言語ではとても奇妙に思われます。事実、Perlを除いた他言語で同様のコードを書いた場合は42
を表示することでしょう。
これは代入だけではありません。関数の引数とした場合も同様です。
<?php
function f($b) {
$b[0] = 42;
}
$a = [2, 3, 5];
f($a);
echo $a[0], "\n";
これも2
です。しかし、他言語では42
です。なぜ、そんなことが起きるのでしょうか?
PHPでは変数は配列そのものの値が入っていると見なされます。そして、代入ではその配列の値そのものが別の変数にコピーされます。関数の引数として渡したときも同様に配列の値そのものが渡されます。PHPでは配列が値型として扱われると言うことです。
両方のコードにおいて、$b
は$a
のコピーであり全く独立して存在します。$b
の中身を変更しても、コピー元である$a
は変更されません。ですので、$a
のインデックス0の値は変わらず2
のままになります。4
しかし、他言語、例えばC#、Java、Python、Ruby、JavaScript等は変数に配列の値ではなく、配列オブジェクトへの参照という値(Javaでは参照値と言いますが、他言語でも同様の名前があるわけではありません)が入ります。そのため、代入や関数への引数において、渡されるのは配列の値そのものではなく、配列オブジェクトへの参照という値です。これらの場合は、配列が参照型として扱われると言うことです。
値を渡すやり方を値渡し(pass by value)、参照という値を渡すやり方を参照の値渡し(call by sharing)といいます。PHPでの配列の処理は(後述の参照渡しをしない限り)値渡しになると言うことです。
また、PHPには上とは別に参照渡し(call by reference)があります。
<?php
$a = [2, 3, 5];
$b = &$a;
$b[0] = 42;
echo $a[0], "\n";
この場合は42
になります。しかし、参照渡しの参照は変数のエイリアスであり、参照の値渡しとは異なる動作です。
Perl(Perl6含む)はPHPと同じく値型として配列を扱います。C++は少し特殊で、C由来の配列はポインタの値としてか扱えませんが、STLのコンテナはオブジェクトそのもの値として扱うことができます。
オブジェクト指向
トレイト
影響度: ★★☆☆☆
PHPには本物のトレイトがあります。多重継承の言語ではそもそも必要ではないので省きますが、他の単一継承の言語(Java、Scala、Ruby等)のほとんどはMix-inしか持っていません。
トレイトとMix-inは実現したいことの方向性は同じですが、細部が異なります。この細部に足下をすくわれる可能性があります。
幸運なことに、トレイトはできたばかりの機能であるため、使っていないと言うことがあるかも知れません。
モジュール・名前空間
include/require vs include_once/require_once
影響度: ★★★☆☆
PHPで他ファイルを読み込むときに使うのはinclude
やrequire
を使います。この二つはエラー止まるか止まらないかの違いですが、どちらも同じファイルを何度でも読みます。それを一度きりにするのがinclude_once
とrequire_once
です。
PHPではinclude
/require
の方が普通に見えます。なぜなら、短いからです。onceつきは余計な機能と思われます。しかし、他の言語では、一度しか読まない方が普通です。同じ処理を何度も読み込むのは無駄と考えています。ですが、PHPは同じ事を繰り返したいときに同じファイルを何度でも読み込みたいとするのが普通なのです。
優れた入門書であればinculde
とrequire
を使ってはいけないとしているかもしれません。しかし、そうではないと教えられてしまったときに、他言語でも何度も読み込むのが普通だと思ってしまいます。
namespace、use、区切りの\
影響度: ★★☆☆☆
namespaceの考え方はJavaに近い物があります。しかし、useの使い方は全くの別物です。ましてや、区切り\
を使うというのはまるでWindowsを思わせるぐらい珍しい物です。
その他
ここらへんで力尽きました。php.iniの話や、モジュールの管理の仕方、曖昧な==
など色々あるのですが、そのうち書き足したいです。
PHPから学んでしまった人へ
You cannot give
up just yet...
PHPer!
Stay determined...
PHPから違和感なく学べる言語はあるのでしょうか?あるとしたらPerlぐらい?Perl5は古すぎますし、Perl6は新しすぎます。もし、過去に戻れるなら、そう、リセットボタンを押せるなら、最初にPHPを選ばなかった方が良かったのかも知れません。誰も傷つけなければ、そう、誰も死ぬことのないエンディングを迎えていたのかも知れません。でも、それは可能なのでしょうか?既に僕らはEXPを得てしまっているし、一度上がってしまったLOVE5を下げる方法はありません。僕らにはリセットボタンがありません。SAVEはできてもLOADはできないのです。だから、何度もSAVEしていくしかないのです。そう、何度でも、いつか自分自身をSAVE(救済)できるように。
さて、元ネタ6を知らない人には何を言っているのかわからないかも知れませんが、a bad timeを迎えるにはまだ早いです。君は既にPHPが他の言語とどのように異なるかの一端を知ることができました。つまり、これまで書いてあったことはPHP特有の本当に特別な事として、一旦忘れてください。それを踏まえて他の言語を学べば良いのです。
多くの言語を知ることは決して悪いことではありません。単に使える言語という手段が広がると言うだけではなく、使えていたと思っていた言語に深みが増すからです。細かいメモリ管理を知らなくてもPHPはできるでしょうが、一度Cを学んでメモリ管理のなんたるかを知れば、より深く知ることができます。関数型プログラミングを知らなくてもPHPは書けるでしょうが、Haskellでもやって関数型とはなんぞやと言うことを学べば、関数型の考え方で設計をするなどの視野が広がります。Javaでオブジェクト指向をやり直すのも良いでしょう。抽象クラスやインターフェースの本当の意味を知ることになるかも知れません。
そうやって、他の流派を渡りながら武者修行した後に、PHPに戻ってきてください。うわ、なんてイケテナイ言語だと文句を言いながらも、以前よりずっとPHPを使いこなし、以前よりもずっと大好きになるはずです。中にはそのまま帰ってこなかった人達7もいますが、君ならきっと次のPHPを担う人物として帰ってくることを私は信じています。
異説:実はPHPすらできていない
どこかのコードをコピペするだけでPHPは動いてしまうことがあります。そのため、コピペだけしかできない人達を量産したと言われています。これはJavaScript+jQueryにおいても言われていることです。未だにjQueryとJavaScriptは別物だと思っているぐらい、そちらは酷いですが、PHP界隈も大概です。
そのような人達はPHPができるとは言いません。PHPerでも何でも無く、プログラマー以前の人達であり、ただの素人さんです。そんな人達が他の言語を学べるようになるなんて事は、当たり前ですが、あり得ない事です。この文章は、そのような人達については完全に除外しており、対象とはしておりません。
【追記】
都市伝説は都市伝説に過ぎないことをが証明されました。
とてもよくまとまっている記事です。Python3を学び始めようとしている、または、Python3を学び始めているけどよくわからないことがでてきている、そんなPHPerにお勧めです。PHPとPython3でどんな違いがあるのか、どういった点が引っかかりやすいのか(勘違いしやすいのか)が見えてくると思います。
でも、上記記事の著者も参照渡しと配列が値型という所について多少混乱(混同?)していたようです。やはり、ここら辺がPHPの鬼門なのかも知れません。間違っていた所は随時修正されていくと思いますが、記事のコメントも一通り読んでおくといいでしょう。
-
初出はteratailでの私の回答です。文言は回答用にその場で考えた物ですので、引用元なんて物はありません。 ↩
-
一連の流れはPHPをDisってるブログエントリを集めてみた - kなんとかの日記が参考になります。Matzの発言前が第1回大会、Matzの発言からが第2回大会となっています。PHP7.0リリース後が第3回大会で現在も継続中です。(私の脳内設定では) ↩
-
言語自体に無いと言うことです。JSP、ASP、ERBはライブラリ等が提供している機能であって、言語そのもの機能ではありません。 ↩
-
実際のPHPの実装では配列のコピーについてコピーオンライト(Copy-On-Write)という仕組みを採用しています。このコピーオンライトの仕組みにより、代入や引数として渡した時点ではメモリ上のデータがコピーされることはなく、コピー元やコピー先で内容の変更があったときにメモリ上のデータがコピーされます。このため、途中で変更を伴わない場合は、多くの代入や引数渡しが行われても配列のデータ量に応じて遅くなると言うことはありません。 ↩
-
Level Of Void Error ↩
-
彼らがどこに行ったのかは誰も知らない…事になっています。決して、PHPがいやn(回線が切断されました ↩