月別アーカイブ: 2013年4月

PHP、JavaScriptで可変長引数を、関数内で引き渡す

可変長引数が抱える心の闇

PHP、JavaScriptそれぞれの可変長引数サポートという記事で扱ったテクニック、可変長引数。関数定義でarg1,arg2,arg3…と続ける事無く、スマートに引数を取り出す事が出来ます。

しかしながら、引数にローカル変数を充てないという事は、関数内での引数の引き渡しで困難に出会います。

//PHP
function dullfunction(){
$args = func_get_args();
otherfunction($args);
}
 
//想定した動作をしてくれません。

動作が想定通りいかないのは、当たり前の事です。この書き方ですと、関数otherfunction()に配列$argsへの参照が与えられるだけで、したがってotherfunctionの中では$argsを参照した1つの引数が与えられたものと見なされてしまいます。

それならば、引数の一つ一つを$args[0],$args[1]…のように分けて渡せば良い!と思われるかもしれませんが、それは可変長引数という前提に忠実ではありません。

そこで、そもそも関数に引数として配列を渡せないのはおかしいと上司に叛旗を翻してみて下さい。数十社クビになった後、親切な上司が(もしいればですが)教えてくれます。

PHP、JavaScriptで配列を関数の引数として与える方法

PHP、JavaScriptともに、配列を要素分だけ引数として与える方法があります。

//PHP
function dullfunction(){
$args = func_get_args();
call_user_func_array("otherfunction", $args);
}

PHPでは、call_user_func_array()という関数を用います。第一引数には、関数の名前を指定します。このとき()はつけません。また、以下のように第一引数に無名関数をとる事も出来ます。

//PHP
function dullfunction(){
$args = func_get_args();
call_user_func_array(function(){$args2 = func_get_args(); print_r($args2);}, $args);
}
 
//無名関数の中でcall_user_func_array("dullfunction",$args2);とかやってはいけない

ところで、このcall_user_func_array()について、あちこちの説明でまるでユーザが定義した関数でないと動かないかのような誤解を与える表現をしていますが、別に普通の関数(var_dumpとか…)を与えても動いてくれます。便利ですよ。

お次はJavaScriptの場合です。

//JavaScript
function dullfunction(){
otherfunction.apply(this,arguments);
}

可変長だろうがそうでなかろうが、JavaScriptの引数がargumentsで取れるという事は既に説明しました。今回新しく登場したapplyというfunctionオブジェクトのメソッドは、メソッドを呼び出した関数の中の”this”キーワードに別の値を代入するという、火星人が作ったようなよくわからない働きをするメソッドですが、第二引数には配列またはオブジェクトで呼び出し元関数への引数を与える事が出来るようになっています。

//JavaScript
function.apply(引数1,引数2);
 
//引数1:関数内の"this"に充てるもの。ただ可変長引数を与えたいだけならthisを指定し
//この部分の働きを殺す。
//引数2:関数に与える引数

きっと親切に教えてくれる上司は火星人です。即刻辞表を突き付けましょう。


PHP、JavaScriptそれぞれの可変長引数サポート

PHP、JavaSciptの関数処理記述方法

PHPとJavaScriptでユーザ関数の定義をするとき、引数をもつ関数の場合には以下のような書式を用います。

//PHP
function dullfunction($arg1,$arg2){
return $arg1 + $arg2;
}
//JavaScript
function dullfunction(arg1,arg2){
return arg1 + arg2;
}

何の変哲も無い、プログラミング言語普遍の関数定義ですね。これは関数の引数が2つであると決まっている場合の書き方になります。arg1、arg2と書いているのが引数(argument)になります。

定義した関数の数より少ない引数を与える場合

上で例に挙げた関数では、そもそも処理内容的に引数が二つとも揃っていないとエラーになってしまいます。それならば、内部処理が2つの引数を必ずしも必要としない関数であれば、定義より少ない関数を与えても大丈夫なのか、という話ですが、PHPとJavaScriptの場合はそこら辺を柔軟に解決しています。

PHPの場合には、無視される可能性のある引数にデフォルト値を設定する事でエラーを抑えます。

//PHP
function dullfunction($arg1,$arg2=null){
return "ダルい";
}
 
dullfunction(); //これはエラー
dullfunction(100); //$arg1には100が与えられる。$arg2はnull。エラーにはならない。
dullfunction(100,200); //$arg1に100、$arg2に200が与えられる。エラーにはならない。

関数定義中の引数部分で代入が行えるPHPらしい(ある意味破天荒な)解決です。ただし、デフォルト値を与える引数は必ず与えない引数の後に書くようにしないといけません。

JavaScriptの場合、与えられた引数の数が関数定義より少なくても、全く問題がありません。

//JavaScript
function dullfunction(arg1,arg2){
return "ダルい";
}
 
dullfunction(); //問
dullfunction(100); //題
dullfunction(100,200); //ナシ

この辺が入門用言語としてポピュラーな理由かもしれません。WEB制作者の中には、JavaScriptは気付いたら使えるようになっていたという習得歴の方もいらっしゃるかもしれません。私もそうでした。

関数が受け付ける引数の数を無制限にしたいとき(可変長引数)

表題の通り、引数の数を無制限にしたい場合です。たとえば与えられた引数全ての和を求めるというのは、よくある処理です。

PHPの場合は、とりあえず引数を持たない関数として定義しておいて、関数内部から実際に与えられた引数にアクセスする関数を呼び出します。この、あらゆるものを関数として用意してしまうようなところがPHP的だと言えるのですが、まあそれはどうでも良い事ですね。

//PHP
function dullfunction(){
$sum = 0;
$num = func_num_args();
for($i=0;i<$num;$i++){
$sum += func_get_arg($i);
}
return $sum;
}
 
//もしくは…
function dullfunction(){
$sum = 0;
$arglist = func_get_args();
for($i=0;i<count($arglist);$i++){
$sum += $arglist[$i];
}
return $sum;
}
 
dullfunction(100,200...); //お好きな数どうぞ

func_num_args()という引数の数を返す関数と、func_get_arg()/func_get_args()という引数そのものをインデックス指定で/配列として 返す関数があります。

JavaScriptの場合、関数定義とは関数オブジェクトを作るということであり、関数オブジェクトには与えられた引数を自動的に格納するargumentsプロパティがあります。したがって、実は定義式での引数の数の宣言もへったくれも無いわけです。引数が何個来ようが、不真面目にargumentsプロパティにアクセスすればいいのです。

//JavaScript
function dullfunction(){
var sum = 0;
for(i=0;i<dullfunction.arguments.length;i++){
sum += dullfunction.arguments[i];
}
return sum;
}
 
dullfunction(100,200...); //お好きな数どうぞ

PHPもJavaScriptも、かなりゆるい言語であることがわかりましたね。そしてゆるさの性格の違いも、垣間見えたのではないかと思います。


JavaScriptがプロトタイプベース言語だということについて

オブジェクト指向言語も決して一枚岩でありません。西の横綱もいれば、西の高校生探偵もいます。このブログで扱う事の多いPHP、JavaScriptなどについて見てみますと、PHPはクラスベース言語というタイプに含まれるのに対して、JavaScriptはプロトタイプベース言語というタイプに含まれます。JavaScriptのプロトタイプベース言語の方が、幾分マイナーですので、こちらの特徴を追ってみましょう。

プロトタイプベース言語には、クラスの概念が無い

オブジェクト指向についてかじった事のある人間ならば、あらゆるものがオブジェクトで、そのオブジェクトには設計図たるクラスが必要で…といったようなオブジェクト指向のイロハに馴染んでいることと思います。一方JavaScriptが属するマイノリティ、プロトタイプベース言語には、オブジェクトの設計図たるクラスは存在しません。それでは、オブジェクトは何を元にして作られるのか、何を実体化してインスタンスをつくるのかという疑問が当然沸き上がる筈でしょう。

実は、JavaScriptにおけるインスタンス化は、既存のオブジェクトを丸々コピーすることによって行われます。コピー元もまた実体をもったオブジェクトなのです。

はい、メモリの無駄遣い発見!JavaScriptはとんだ低級言語だぜ!と思われた方は、オブジェクト指向の意義についてしっかりと理解された方だと思います。既存のオブジェクトをコピーするという方法では、もしプロパティやメソッドの実装部分が100個あったら、インスタンスそれぞれにつき100個を丸ごとコピーしなければなりません。それでは、毎度毎度新しいオブジェクトを一から作成しているのと何ら変わりません。そこで、プロトタイプ言語ではそうした問題を解決する方法として、プロトタイプオブジェクトという概念を用意しています。

JavaScriptでのクラス(仮)宣言

JavaScriptでクラスのようなもの、つまりインスタンスにコピーされるコピー元のオブジェクトを宣言する場合には、変数(クラス(仮)名)への関数リテラルの代入だけで済みます。

var Conan = function(){};

これで終わりです。右側に書かれた無名関数がコンストラクタになりますので、何かしらプロパティやメソッドを設けたい場合には、function(){}の中に書けば大丈夫です。

var Conan = function(powder){
this.powder = powder;
this.pero = function(){
if(this.powder == "青酸カリ"){
alert("これは…青酸カリ!");
} else {
alert("この料理を作ったのは誰だっ!");
}
};
};

これでクラス(仮)Conanにプロパティpowderとメソッドpero()ができました。インスタンスを作成する際に引数を与えることで、プロパティpowderを初期化できます。

var conan = new Conan("青酸カリ");
conan.pero();
 
//結果 これは…青酸カリ!

JavaScriptのプロトタイプ

さて、先ほど挙げた問題に戻ります。このままですと、Conanクラスのインスタンスをconan1,conan2,conan3…とどんどん作っていくことで、その都度powderプロパティとpero()メソッドの宣言が行われメモリの無駄になってしまいます。そこで、JavaScriptのクラス(仮)には暗黙のプロパティであるprototypeというものが用意されており、このプロパティ(正体はオブジェクト)への値の代入を行うことで、プロパティやメソッドの追加が行えるようになっています。

var Conan = function(powder){
this.powder = powder;
};
 
Conan.prototype.pero =  function(){
if(this.powder == "青酸カリ"){
alert("これは…青酸カリ!");
} else {
alert("この料理を作ったのは誰だっ!");
}
};
 
//宣言してもいないConanクラスのプロパティ prototype に値を入れてしまう

これで、先ほどと同じくインスタンスからメソッドを実行できるようになります。

どの辺りがメモリの節約になったのかと言うと、実はインスタンス側にはpero()メソッドがコピーされておらず、インスタンスからpero()メソッドが呼ばれると、インスタンスの持つprototypeオブジェクトへの参照を便りに、prototypeオブジェクト中の同名メソッドを呼ぶ仕組みになっているのです。
つまりどれだけプロトタイプ側にメソッドやプロパティを追加しても、インスタンス側にはプロトタイプへの参照しか残らないので、経済的だというわけですね。

いかがだったでしょうか。決してJavaScriptの属するプロトタイプベース言語が、クラスベース言語に劣った低級なものでないという事が分かったのではないでしょうか。実はプロトタイプベース言語はクラスベース言語へのアンチテーゼとして作られたという経緯があり、クラスベースのオブジェクト指向の弱点である、開発の段階が進むにつれて設計図であるクラスへの設計図としての要求が高くなってくる(→リファクタリングの必要性が出てくる)といったところをうまく解決して、機能の追加をアドホックに行えるという持ち味を醸しています。

ということで、JavaScriptは案外面白い奴です。