連載
» 2015年06月04日 05時00分 公開

TypeScriptで学ぶJavaScript入門:第11回 関数に関するいくつかのトピック (2/4)

[羽山博,著]

関数のオーバーロード

 オーバーロードとは、同じ名前を持ち、異なる引数リストや戻り値の型を持つ複数の関数を定義することである。関数を呼び出すときに指定する実引数の個数やデータ型によって、実際にどの関数が呼び出されるかが決まる。他のプログラミング言語を知っている人なら、以下のgetLength関数のように書きたくなると思うが、TypeScriptではこのような書き方をしない。実際にはエラーとなるが、オーバーロードの意味を感覚的に理解しやすいので、あえてコードを追いかけてみよう。

 繰り返すが、このコードはTypeScriptとしては正しくないコードであり、コンパイル後のJavaScriptコードを実行しても、その動作はこの後の説明とは異なる。これはあくまでもイメージだと考えてほしい。

function getLength(x: string): number// (1)
  return x.length;
}
function getLength(x: number): number// (2)
  if (x == 0) return 1;
  return Math.floor(Math.log(x) / Math.LN10) + 1;
}
alert(getLength(123));   // (3)
window.close();

オーバーロードを感覚的に理解するためのコード。ただし、間違った書き方(TypeScript)
このコードはTypeScriptとしては正しくないコードであり、コンパイル後のJavaScriptコードは説明のような動作とはならない。これはあくまでもイメージだと考えてほしい(実際に上のコードを実行すると、実引数に文字列を指定しても、二つ目のgetLength関数が呼び出されてしまう。これはJavaScriptでは関数オーバーロードがサポートされていないことによるものだ)。

 (1)は文字列の桁数を返すgetLength関数で、(2)は数値の桁数を返すgetLength関数である。(3)では、getLength関数に「123」という数値を指定して呼び出しているので、この仮想的なコードでは(2)の関数が呼び出される。もし、(3)で引数に文字列を指定すると、(1)の関数が呼び出される。イメージとしては以下の図のようになる。

図2 オーバーロードの一般的なイメージ 図2 オーバーロードの一般的なイメージ
同じ名前の関数がいくつかあるが、指定した引数の個数とデータ型が一致する関数が呼び出される。

 しかし、TypeScriptでは、関数をオーバーロードするには、実装なしで引数リストと戻り値の型だけを指定したオーバーロード関数を続けて書き、最後にいずれの形式の関数呼び出しにも対応できるような実装を書く。よって、以下のコードが正しい書き方になる。

function getLength(x: number): number;   // (1)
function getLength(x: string): number;   // (2)
function getLength(x: any): number {   // (3)
  if (typeof (x) == "string") {
    return x.length
  } else {
    if (x == 0) return 1;
    return Math.floor(Math.log(x) / Math.LN10) + 1;   // (4)
  }
}
alert(getLength(123));
window.close();


 (1)(2)が正しくないTypeScriptコードの例で見た関数の宣言に当たる。関数の実装を{ }の中に書かずに、関数の引数リストと戻り値の型を指定し、最後に「;」を書けばよい。(3)がオーバーロードされた関数の実装になる。仮引数のデータ型はanyとし、number型の値が渡されても、string型の値が渡されてもいいようにしてある。

 「それでは(1)(2)の宣言を書かなくても同じことじゃないか」と思われる向きもあるかもしれないが、(1)(2)を書いておけば、number型もしくはstring型以外の引数を指定するとTypeScriptコードのコンパイル時点でエラーになるので、きちんと型チェックができるというわけだ。

 実際の処理は(3)の関数で実行されるので、その中でデータ型を調べ、文字列の場合と数値の場合の処理を書く。こちらも図にしてイメージがつかめるようにしておこう。

図3 TypeScriptでのオーバーロードの実際のイメージ 図3 TypeScriptでのオーバーロードの実際のイメージ
同じ名前の関数がいくつかあるが、指定した引数の個数とデータ型が一致する関数が呼び出される。

 数値の桁数を求めるには、基本的には10を底としたその値の対数を求めて小数点以下を切り捨て、1を加えるといい。例えば、100は10の2乗なので、10を底とした対数は2になる。それに1を足せば桁数になる。ただし、TypeScriptやJavaScriptには10を底とする対数を求める関数がないので、自然対数を求める関数と、10の自然対数の値を使って結果を求めている。

 このプログラムを実行すると、「3」という結果が表示される。なお、getLength関数では負の値や小数点付きの値は想定していないことに注意しよう。

[コラム] 対数の底を変換するには

 対数は高校の数学で学びますが、遠い昔に忘れたという人も多いでしょう。ある値が、abであるとき、aは底(てい)、bは指数と呼ばれます。その指数の部分を取り出す計算が対数で「log」を使って表します。

 以下のような例を考えてみましょう。「log」の右下に小さく底を書きます。

  log2 8

 この場合、2が底となっています。つまり、「8という値は2の何乗ですか」という意味です。8は2の3乗なので、答えは「3」となります。このように、値が底のべき乗になっていると計算は簡単ですが、そうでない場合はちょっと計算が面倒です。例えば、以下を見てください。

  log2 10

 この場合、底が2なので、10は2の何乗かという意味になります。これは簡単には計算できません(答えは約3.3219です)。計算が難しい場合は、以下の公式を使って底を変換すると、計算しやすくなることがあります。

  logp q = logr q/logr p

 従って、底がe(==2.718...)の対数(自然対数)しか利用できないときに、底が10の対数を求めるには、以下のように計算すればよいことが分かります。

  log10 x = loge x/loge 10

 サンプルコードではこの公式を使って、底が10の対数を求めています。例えば、100は10の2乗で、1000は10の3乗なので、100〜999までの対数は2以上3未満です。こうやって求めた対数の小数点以下を切り捨てて、1を足せば元の値の桁数になるというわけです。


[コラム] 底が10の対数を求めるための関数

 ECMAScript 6では、底が10の対数を求めるためのlog10関数が提案されていますが、現時点では一部のブラウザーでしかサポートされていません。


 ところで、オーバーロードしようとする関数の、引数の個数が異なる場合もあるだろう。そのような場合には、最初に見た省略可能な引数を使って関数を実装すればよい。以下の例は、文字列の長さを文字単位で数えるか、byte単位で数えるかを指定できるようにしたものである。引数isCharUnitがtrueの場合には文字単位、falseの場合にはbyte単位とし、省略時には文字単位が指定されたものと見なすことにする。

function getLength(x: number): number;
function getLength(x: string, isCharUnit: boolean): number;   // (1)
function getLength(x: any, isCharUnit = true): number {   // (2)
  if (typeof (x) == "string") {
    if (isCharUnit) {
      return x.length;
    } else {   // (3)
      var sum = 0;
      for (var i = 0; i < x.length; i++) {
        if (x.charCodeAt(i) < 256) {
          sum += 1;
        } else {
          sum += 2;
        }
      }
      return sum;
    }
  } else {
    if (x == 0) return 1;
    return Math.floor(Math.log(x) / Math.LN10) + 1;
  }
}

alert(getLength("abc日本語", false));
window.close();


 (1)では、最初の引数が文字列型で、2番目の引数がブール型の関数getLengthを宣言している。(2)では、2番目の引数が省略された場合にはtrueが指定されたものとして、関数を実装している。

 関数の処理では、(3)以降がバイト数を求める処理になる。ここでは、プログラムを簡単にするためにcharCodeAt関数で求めた文字コードが256未満であれば1byteとし、それ以上であれば2bytesとしている。単に半角英数字でなければ2bytesと数えているだけなので、実際のバイト数になっていないこともある(1文字が3bytes以上で表されている場合には、実際のバイト数にはならない)。

 結果は以下の通り。なお、getLength関数の実引数として、数値と真偽値を指定するとエラーになる。

図4 引数の個数が異なる、オーバーロードされた関数の利用例 図4 引数の個数が異なる、オーバーロードされた関数の利用例
getLength関数の第2引数にfalseを指定した場合の実行例。文字列のバイト数を表示する。ただし、ここでは単純に半角英数字以外を2bytesとして計算している。

 次にジェネリックスと呼ばれる機能について見てみよう。

Copyright© 1999-2018 Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。