技術memo

関数型ゴースト

「同じようなswitch文があちこちにあると改修が困難」って本当?(Expression Problem)

概要

プログラムの設計では、よく「同じようなswitch文があちこちにあると改修が困難になる」と言われています。そういった場合は、「switchの分岐を、オブジェクトの多態性を利用して無くすべきだ」とも言われます。

しかし、それは本当でしょうか。「すべての分岐を記述される前に消し去りたい」、そんな極論は許されるのでしょうか。今回はそれについて考察します。

用語

  • 分岐条件

    • 列挙される場合分けの種類。あるいはその一覧。
    • ケース、データ型とも呼ぶ。
  • 処理の種類

    • 場合分けに対応する、特定の場合に実行する処理。
    • 関数、メソッドとも呼ぶ。

一足飛びの結論

  1. 条件節による分岐は、分岐がどこに属するかを個別に決められますが、分岐条件の増減による影響が大きくなりがちです。ですが、処理の種類が増減する場合は、改修は容易です。

    • switch文を使う場合
    • 代数的データ型のパターンマッチを使用した場合。
  2. 多態性を利用した分岐の事前決定は、すべての分岐について一箇所で判定する必要がありますが、「この条件の場合の全ての処理」という形で記述がまとまっているため、分岐条件の増減には対応しやすいです。しかし、処理の種類が増減する場合は、分岐の全ケースについて改修を行う必要があります。

    • クラスの継承を使う場合。
    • メソッド集合としてのオブジェクト(レコード)を動的に生成する場合。
    1. 2.から、また分岐条件と処理の種類のどちらが変更されうるか、モジュールとして分岐がどこに属するべきかを考慮して選択するべきです。
  3. これらからわかるように、ここでの話題はExpression Problemです。1. 2.のどちらもでも、これらの素朴な方法では、データ型(分岐条件)の追加と関数(処理の種類)の追加の両方を容易に(再コンパイル無しに)し難いということがわかります。

  4. 素朴な方法ではない解決とは、例えば型クラスや、多相ヴァリアント、多相レコードを利用する方法があるようですが、ここでは取り扱いません。

続きは具体例です。

続きを読む

SML# の「多相レコードによる多相ヴァリアントの表現」がわからなかった話

この投稿は、ML Advent Calendar 2014の22日目の記事です。前日はkawada_syogo225さんのAlice MLの仮想マシンについて | 小さいモノは美しいでした。

多相ヴァリアントの話

ラベル付きヴァリアントの多相版、です。詳しくは以下あたりを見ましょう。

雰囲気的には、ラベルの属する型に関係なく、複数ラベルを一纏めにして扱ったりできるようなイメージです。

多相でないラベル付きヴァリアントなら、F# の判別共用体やHaxeenumのように、幾つかの言語に導入されていますが、多相バージョンは現状ほとんどOCaml専用のようです。

多相レコードの話

SML# のキラーコンテンツ、もとい主力機能です。詳しくは公式の解説を読みましょう。

と言っても、これだけでは普通のレコードと何が違うのか今ひとつわからないので、簡単にまとめておくと

  • 通常のレコード型
    • 型に属するフィールドを全て一度に宣言する (例(こちらから借用): type Point = { x : float; y: float; z: float; })
    • フィールド取り出し操作は「ある特定の単相的なレコード型 -> 指定されたフィールド名のフィールド型」の関数になる
  • 多相レコード型
    • 型宣言しなくても使える
    • フィールド取り出し操作は「指定されたフィールドを持つ任意の(多相的な)レコード型 -> 指定されたフィールド名のフィールド型」の関数になる

多相レコードによる多相ヴァリアントの表現

SML# の公式ドキュメントに、次のページがあります。

抜粋すると

  • レコードが名前付きのデータの集合であるのに対して,ラベル付きバリアントは,名前付きの処理の集合の下での処理要求ラベルのついたデータと考えることができます.

  • つまりタグTをもつバリアントは,タグTを処理するメソッドスイートを受け取り,自分自身にその処理を呼び出す行うオブジェクトと表現します. あとは,必要な処理をそれぞれの表現に応じて書き,タグに応じた名前をもつレコードにすればよいわけです.

  • レコードによるバリアント表現の基本は,そのデータを処理する関数を引数とする高階の関数として表現することです.型システムが,その関数の多相性を正確に表現できれば,多相型バリアントも自然に表現できます.

  • fn M => #Age M 21

  • この関数は,種々のバリアントを処理するメソッドスイート Mを引数として受け取り,そのなかから"Age"用のメッソドをディスパッチします.

... つまり、どういうことでしょうか?

例としては極座標と直交座標が挙げられていますが、正直よくわかりません。多分私の理解力が不足しているのだと思います。それに、この程度なら多相ヴァリアントでなくても単相で十分に見えます。

ということで

もうちょっと複雑で実践的な例をやってみました。

続きを読む