モジュールの集約に多相バリアントを使ってみた

OCamlならではの型機能である多相バリアントプライベート列型を「なんとなく」使ってみた話.タイトルはやや(かなり?)大げさで,小ネタです.
追記: プライベート列型は使わなくて済んだ…

ストーリー

仕事でOCamlを使っている. ボスに命ぜられた通り,数週間をかけて製品の色々な機能をモジュール単位で作り上げた(と仮定する). ここにその成果である金融相場のテクニカル分析モジュール一式がある.

(* テクニカル分析を表現するシグネチャ. 関数はすべて純粋にすること *)
module type Analysis = sig
  type t
  val calc : raw list -> t (* 生データから計算 *)
  val recalc : raw list -> t -> t (* 前回からの差分より再計算 *)
  val series : t -> (datum * time) list (* 時系列で分析結果を取得 *)
end;;

(* 移動平均 *)
module MovingAverage : Analysis = struct
  type t = ...
  let calc = ...
  let recalc = ...
  let series = ..
end;;

(* 一目均衡表 *)
module Ichimoku : Analysis = struct
  type t = ...
  (* 略 *)
end;;

(* その他のテクニカル分析モジュールが続く… *)

ボスはこう言う:


最後にmain.mlファイルを作って、そこからアプリケーションを起動するようにすればOK。
なるほど. 私はmain.mlで,こう書いた:

(* Main.t型 *)
type t = MA of MovingAverage.t | IC of Ichimoku.t | ...
(* 現在表示中のテクニカル分析 *)
let current_techs : t list ref = ref []

ちょっと気になる…

わりとどうでもいい話だが,…この type t = MA of MovingAverage.t | IC of Ichimoku.t | ... という長ったらしい型宣言が気に入らない.複数のモジュールの型tの直和型を作ることだけを目的に,長く余計な型とコンストラクタを導入している.
これを解消するため(?)に, 多相バリアントと private row type を使って次のようにしてみた.

module type Analysis = sig (*変更なし*)
  type t
  val calc : raw list -> t
  val recalc : raw list -> t -> t
  val series : t -> (datum * time) list
end;;

module MovingAverage : Analysis with type t = [`MovingAverage of s] = struct
  type t = [`MovingAverage of s] (* ↑with type t = … で多相バリアントを露出 *)
  let calc = ... 
  (* 略.適当に書き直す *)
end;;

module Ichimoku : Analysis with type t = [`Ichimoku of s] = struct
  type t = [`Ichimoku of s] (* ↑with type t = … で多相バリアントを露出 *)
  (* 略 *)
end;;

こうすれば,先ほどの Main.t は

type t = [Ichimoku.t | MovingAverage.t | ...]
let current_techs : t list ref = ref []

と書ける.割とスッキリしたのではないかと思う.複数のモジュールに分散した多相バリアントのタグを混ぜ合わせて一つのバリアントを作った感じだ.上のとやってることは変わらないが,今度は型Main.tがあたらしく作った型でなく既存のtのサブタイプになっている(→coercionが必要だけど).やったぜ! ちょっとした変更ながら喉の小骨が取れたようですごくスカッとする! …のは私だけかもしれない….こんな多相バリアントの使い方もアリかもね,ということで.

雑感

複数のMLモジュールをいかに集約するか,についてはもう少しちゃんとわかりたいと思っている*1.上の話は実際にはOCamlのオブジェクトを使えばよりすっきりと書けるように見えるし*2 *3オブジェクト指向言語だとそれ以外に手はないのだが,今回はせっかくMLで仕事をしているのとid:osiireさんがモジュールに関する記事を書いたこともありモジュールにこだわってみたかったのと,あとは宗教上の理由から敢えてそうしなかった. そのうちまた何か書くかもしれない.
多相バリアントは使いこなせればコードの再利用を促進するすばらしい言語機能だと思います.この記事ではほとんど触れていませんが,興味のあるかたは是非使ってみてください.

*1:first class moduleも含めて…

*2:参考:OCamlで存在型ならオブジェクト、Haskellなら型クラス

*3:社内でもそう指摘された