このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

「アドホック多相 vs パラメトリック多相」をマジメに考えてはいけない

アドホック多相」、「パラメトリック多相」という言葉は、覚えておけば便利に使えます。が、これらの言葉は、軽率に気分的・雰囲気的に使うものであり、違いや定義をマジメに議論すべきものではありません。

内容:

nLabを見てもモンヤリ

nLab(https://ncatlab.org/nlab/show/HomePage)は、数理科学*1の概念・用語に、できるかぎり圏論的な、できるかぎり厳密な定義を与えようとしているサイトです。そのnLabでも、「多相〈polymorphism〉」のエントリーには、よくある説明しか載ってません。

多相〈polymorphic〉な関数とは、単一の名前に複数の意味があるような関数だ、と、そんなことです。

単一の名前に複数の意味がある状況を二種類に分けて、

  1. 単一の名前が、複数の異なる関数を指している。
  2. 単一の名前が指す(単一の)関数が、複数の異なる型で使用できる。

一番目の状況をアドホック多相〈ad-hoc polymorphism〉、二番目の状況をパラメトリック多相〈parametric polymorphism〉と呼んでいます。

名前の同一性はハッキリした概念ですが、関数の同一性はハッキリしない概念です*2。ハッキリしない概念を使って説明されているアドホック多相/パラメトリック多相も、もちろんハッキリしない概念です。

ハッキリさせようと試みるのは良いことですが、アドホック多相/パラメトリック多相に関しては、ハッキリしないモンヤリした概念だと知った上で、カジュアルに(雰囲気的にテキトーに)使えばいいんじゃないか、と僕は思います。

オーバーロード、多相、総称

「多相」に類似していて、しばしば同時に登場する言葉に「オーバーロード」、「総称」があります。これらの言葉も区別したり、それぞれを厳密に定義したい人もいるでしょうが、あまり建設的じゃない。ありていに言えば、不毛です。

オーバーロード」、「多相」、「総称」はプログラミングの文脈で出現する(ことが多い)言葉ですが、そういう言葉はだいたい、プログラミング言語ごとに定義が違うのです。方言がたくさんあって、標準語はない状況なんですね。そんな状況で、「俺が使っている言葉こそは標準語だ」と訴えるのは無意味であり、ひんしゅくを買うだけです*3

個々のプログラミング言語からは離れた一般論としては、「オーバーロード」、「多相」、「総称」を区別/定義できても嬉しいことはないので、区別/定義する動機がありません。日常語と同じレベルで曖昧に恣意的に使うだけでしょう。

僕自身の曖昧で恣意的な使用法は次のようです; 僕は、言葉の多義的使用は何でも「オーバーロード」と呼びます。例えば、「一休さん」のおはなしのなかに、「このハシわたるべからず」に対して「橋の真ん中を渡った」というエピソードがあります。これは、「ハシ」と発音する日本語の多義性「ハシ → 橋、ハシ → 端」を利用したものです。つまり、日本語「ハシ」はオーバーロード〈多義的使用〉されています。

オーバーロード〈多義的使用〉されている言葉が、関数または関数類似物の名前であるとき、僕は「多相」といっています。オーバーロードを解決する(複数の意味からひとつを選ぶ)ときに、型の情報が使われる多相は「総称」がシックリくる気がします(個人的気分)。

多義的名前と識別インデックス

「名前 → 意味」という対応が、名前ひとつに意味ひとつなら多義的ではありません。「名前 → 意味1、名前 → 意味2、……、名前 → 意味n」(n ≧ 2)のときが多義的です。意味のほうに出てきた番号 1, 2, ..., n をインデックス〈index〉と呼びましょう。インデックスを使って、名前から意味への対応を書けば:

  • 名前[1] = 意味1
  • 名前[2] = 意味2
  • ……
  • 名前[n] = 意味n

となります。例えば、

  • ハシ[1] = 橋
  • ハシ[2] = 端
  • ハシ[3] = 箸

多義的言葉でも、インデックスを指定すれば意味を特定できます。このことから、多義的言葉は、インデックスに応じた意味を持つといえます。

インデックスは無限個あってもいいし、数ではない他の何か(文字列とか型とか)でもいいとしましょう。

多義的言葉が関数名である例を出しましょう。まず、基本的な関数として:

1. 与えられた2つの数を掛け算する関数を multiply(x, y) とします。
2. グラフィックスで、与えられた左上点座標と幅・高さを持つ矩形オブジェクトを生成して返す関数を Rectangle(top_left_x, top_left_y, width, height) とします。
3. 与えられた行数・列数を持ち、成分はすべて0で初期化された行列(2次元配列)を生成して返す関数を Matrix(n_rows, n_columns) とします。

これらの関数を利用して3つの関数を定義します。構文はJavaScript風です。

/* square[1] 二乗する関数 */
function square(x) {
  return multiply(x, x);
}

/* square[2] 正方形を生成する関数 */
function square(x) {
  return Rectangle(0, 0, x, x);
}
/* square[3] 単位正方行列を生成する関数 */
function square(n) {
  var m = Matrix(n, n);
  for (i = 0; i < n; i++) {
    m[i][i] = 1;
  }
  return m;
}

3つの関数はすべて同じ名前 square を持ちます。が、3つの中身はまったく違います。実際のJavaScript言語処理系では、同じ名前で異なる3つの関数を定義できませんが、ここでは、同じ名前で異なる3つの関数が定義できて、それらを区別したい場合はインデックス 1, 2, 3 をブラケット('[' と ']')内に入れればいいと想定しましょう。

これは、関数名オーバーロードの例であり、オーバーロードを明示的に(プログラマが人手で)解決するには、インデックスを指定することになります。

  • square[1](4) の値 = 16
  • square[2](4) の値 = (0, 0)を左上として 幅=高さ=4 である正方形オブジェクト
  • square[3](4) の値 = サイズが4×4の単位行列

オーバーロード解決のために、毎回手でインデックスを指定するのはめんどくさいですね。そこで、インデックス指定せずにどうやってオーバーロード解決するか? が問題になります。しかしながら、オーバーロードや多相が生じる現象・状況の話と、オーバーロードの自動解決の手段・メカニズムの話は切り離せます。むしろ、ゴッチャにすると混乱します。ここでは、オーバーロードの自動解決の話はしません

アドホック多相は複数の関数定義(なの?)

前節のsquareは、偶発的に同じ名前になってしまった例です。同じ名前にすべき強い根拠はありません。同じ名前にして便利ということもさほどありません。こういう多相(単一の関数名が複数の意味を持つこと)は、アドホック多相と呼びます。

アドホック多相のプログラムコード上の特徴は、関数定義が複数あることだとされています。実際、前節のsquareは、3つの関数定義がありました。しかし、関数定義の書き方なんて色々あるわけで、書き方によって関数定義がひとつだったり/たくさんだったりと変わると思いませんか? 実際、変わります。

もうひとつ別なアドホック多相(かも知れない)例を出します。increase(増やす、盛る)という名前で2つの関数を定義します。型の指定はTypeScript風です。

/* increase["num"] 数を1増やす関数 */
function increase(x:number):number {
  return (1 + x);
}
/* increase["str"] 語(文字列)の前に"more"を付ける関数 */
function increase(x:string):string {
  return ("more " + x);
}

今度は、インデックスに文字列 "num", "str" を使うことにしましょう。例えば:

  • increase["num"](4) の値 = 5
  • increase["str"]("beautiful") の値 = "more beautiful"

ちなみに、次のコードを使うと、実際のJavaScript処理系でも increase["num"](4), increase["str"]("beautiful") という呼び出しができます。

function isNumber(x) {
  return typeof(x) === "number";
}
function isString(x) {
  return typeof(x) === "string";
}

var increase = {
  "num" : function (x) {
    if (isNumber(x)) {
      return 1 + x;
    } else {
      throw new Error("Type Error");
    }
  },
  "str": function (x) {
    if (isString(x)) {
      return "more " + x;
    } else {
      throw new Error("Type Error");
    }
  }
}

さて、この2つの関数定義をひとつにまとめることはできるでしょうか? 次のようにすれば関数定義はひとつになります*4

function increase[i](x) {
  return (a[i] + x);
}

aは、多相な定数で、インデックスiにより値が変わります。

  • a["num"] = 1
  • a["str"] = "more "

実際のJavaScript処理系で動くように細工したもの(なんちゃって実装)は次*5increase[i](x) の代わりに、increase(i, x) として呼び出します。

var a = {
  "num": 1,
  "str": "more "
}

function increase(i, x) {
  return (a[i] + x);
}

関数定義がひとつで済む多相は、パラメトリック多相だということになっています。increase は関数定義ひとつでも済んだので、パラメトリック多相な関数なのでしょうか? でも、もともとは2つの関数定義だったし、同じ名前にする必然性も弱いし …

ここで、「アドホック多相、パラメトリック多相、いったいどっちなんだろう?」と真剣に考えるのではなくて、「アドホック多相とパラメトリック多相の境界線なんて、曖昧でイイカゲンなものなんだな」と了解すればいいのです。

それでもマジメに考えたいのなら

「マジメに考えてはいけない」と言われても「マジメに考えたいのだ」というマジメな人がいるかも知れません。おそらく2つのアプローチがあるでしょう。どちらの方法も、「アドホック多相とパラメトリック多相の境界線」を引くものではなくて、アドホック性(パラメトリック性でもいいが)の程度を測ろうとする試みです。

今までの話で分かるように、多相関数は、インデックスで識別される関数の集まりです。つまり、関数のインデックス族〈indexed family of functions〉とみなせます。関数のインデックス族は、インデックスの集合〈indexing set〉から関数の集まりへの写像です。

この関数のインデックス族の概念を一般化すると自然変換になります*6。多相関数を自然変換として定式化して、自然変換のあいだの関係をアドホック性を計る尺度に用います。実際、いくつかの方法で自然変換のあいだの順序を定義できます。その順序は、アドホック性の程度とみなせます。

別なアプローチは、同じ名前を持つ関数(の実体)達は、なんらかの共通性を持つだろう、と考える方法です。共通性を指標・仕様〈signature/specification〉として定義します*7。多相関数が、どのような指標・仕様によって記述されるか、言い方を変えれば、どのような指標・仕様に“所属”するかに注目します。

多相関数のアドホック性を測るために、多相関数そのものではなくて、それが所属する指標・仕様のあいだの関係性(より一般/より特殊)を利用します。よりリッチな機能を持つ/より制約の強い指標・仕様に所属する関数ほどアドホック性が低いとみなせます。

上記の2つのアプローチをミックスまたは統合することもできるでしょうし、もっと他のアプローチがあるかも知れません。

いずれにしても、通常いわれている「関数定義がひとつか複数か?」のような曖昧でイイカゲンな定義からは、曖昧でイイカゲンなことしか言えません(それでもいいと僕は思いますが*8)。より厳密な議論をしたいなら、より厳密な定義をスタートラインにする必要があります。

[追記]マジメに議論しても不毛な別な例:並列処理 vs 並行処理

[/追記]

*1:当初は、高次圏論の概念・用語を対象としていましたが、今では守備範囲が広がって、数学一般、物理、計算科学なども扱っています。

*2:集合論内での関数〈写像〉とその同一性の概念はハッキリしています。が、それは超越的同一性です。プログラムコードで記述された関数の同一性、あるいは関数を記述するプログラムコードの同一性は、それほど単純な話ではありません。

*3:プログラミングの文脈に限らず、専門用語の定義は、その用語を使う集団のローカルルールにより支えられています。よって、「これこそがスタンダード」のような物言いは、頭の悪さを印象付けてしまうので注意しましょう。

*4:インデックスiの値により関数の入出力の型が変わります。依存型を持つ関数になります。

*5:2つの関数をひとつにまとめることが出来た理由のひとつに、JavaScriptのプラス記号が最初から演算子オーバーロードされていることがあります。記号'+'が、+["num"] と +["str"] に、暗黙のインデックス付けされるのです

*6:パラメトリック多相関数は自然変換になるが、アドホック多相関数は自然変換にならない、と思っている人がいるかも知れません。そんなことはありません。アドホック多相関数(と呼ばれているもの)でも自然変換に仕立てることができます。

*7:指標・仕様という言葉の使い分けについては「指標と仕様」。

*8:二分法〈dichotomy〉の基準とならなくても、傾向性を表す言葉として使えます。「理系 vs 文系」とかも、ほぼ無意味だと思いますが、それでも会話のなかで使われています。