モジュールシグニチャの多重継承
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は意外と非常に便利な機能である。