モジュールシグニチャの多重継承
module type ADD = sig type t val add : t -> t -> t val to_string : t -> string (* ここ *) end
のようなシグニチャを書いたが、書いていて「うーん、これはおかしいな」と思った。
MULでto_stringが必要だったからといってADDのインターフェースに追加するような関数ではない。
本当は
module type ADD = sig type t val add : t -> t -> t end module type PRINT = sig type t val to_string : t -> string end module MakeMul ( M : ???) = struct ... end
と書きたかったのだが、ここでMをADDでもありPRINTでもあると表現する???部分に当たるシグニチャの書き方に詰まってしまった。OOPでいうところのインタフェースの多重継承にあたる機能・構文である。
OCamlのモジュールシステムで継承といえばincludeなのだが、例えばこういうのはうまくいかない:
module MakeMul (M : sig include ADD include PRINT end) = struct ... end
Error: Illegal shadowing of included type t/??? by t/???などとエラーが出る。ADDでtype tが定義されるのをincludeした後にPRINTでも同じくtype tがincludeされて衝突してしまう。
そこでdestructive substitutionの出番だ!と
module MakeMul (M : sig include ADD include PRINT with type t := ADD.t end) = struct ... end
としてみたのだがError: Unbound module ADDと怒られてしまう。ストラクチャ(モジュール実装)の方だとtype t := ADD.tでいけると思うのだが、シグニチャだとADD.tのようにアクセスできないのが問題。
もうちょっと調べてみたところ:
このStackOverflow記事を見つけ、そこでこのReal World OCamlの箇所について知った:
すでにinclude ADDしてるのでinclude PRINT with type t := tでいい:
module MakeMul (M : sig include ADD include PRINT with type t := t (* 二番目のtはADDから来ている *) end) = struct ... end
include PRINT with type t := tが何をしているかというと、PRINTシグニチャに出てくるtへの言及を現在のスコープにあるtに置き換えた上でPRINT内のtype tという定義を消し去ってしまう。これでto_stringはADDに定義されているtからstringへの関数になる。
しかしRWOで書かれている通り、確かに:
module MakeMul (M : sig type t include ADD with type t := t include PRINT with type t := t end) = struct ... end
の方がADDもPRINTも統一的に扱っていていい感じだ。こちらを使っていきたい。
ちなみに
module MakeMul (M : sig type t include ADD with type t = t include PRINT with type t = t end) = struct ... end
とtype t = tとしてしまうと
Error: Multiple definition of the type name t.
Names must be unique in a given structure or signature.
とエラーになる。includeで継承する時は大抵=ではなく:=でdestructive substitutionを使う必要がある。
最初に知った時は「なんだこの変な機能は」と思ったものだが、destructive substitutionは意外と非常に便利な機能である。