SMLのファンクタに少し踏み込んだ

κeenです。SmlSharpContribにコントリビュートしてます。そこでファンクタを使う用事があったのですが少し踏み込んだ使い方をしようとしたらハマったのでメモ。

ファンクタおさらい

SMLのfunctorstructureに引数がついたもので、モジュールを引数にとり、モジュールを返します。

functor List (Args : sig type elem end) =
struct
  type elem = Args.elem
  datatype list = Nil | Cons of elem * list
  fun length Nil = 0
    | length (Cons (x, xs)) = 1 + length xs
end

structure IntList = List(struct type elem = int end)

複雑なファンクタ

以前mlyaccを使った時Joinなる3つのモジュールを引数にとるファンクタが登場したのでした。

structure PrologParser =
Join(structure LrParser = LrParser
     structure ParserData = PrologParserLrVals.ParserData
     structure Lex = PrologLex)

これの定義を覗いてみます。

functor Join(structure Lex : LEXER
             structure ParserData: PARSER_DATA
             structure LrParser : LR_PARSER
             sharing ParserData.LrTable = LrParser.LrTable
             sharing ParserData.Token = LrParser.Token
             sharing type Lex.UserDeclarations.svalue = ParserData.svalue
             sharing type Lex.UserDeclarations.pos = ParserData.pos
             sharing type Lex.UserDeclarations.token = ParserData.Token.token)
                 : PARSER =
...

複数のモジュールの他にsharingなるキーワードも出てきています。それにstructureキーワードもプリフィクスされています。

先程の例とは大分離れてますね。何があったのでしょう。structureを付けとけば複数書ける…?

省略記法

実はファンクタの引数の中では省略記法が使えます。引数のモジュール名とsig ... endが省略可能なのです。さらに適用の時もstruct ... endも省略可能なのです。

つまり、最初の例はこうも書けるのです。

functor List (type elem) =
struct
  type elem = Args.elem
  datatype list = Nil | Cons of elem * list
  fun length Nil = 0
    | length (Cons (x, xs)) = 1 + length xs
end

structure IntList = List(type elem = int)

モジュール内モジュールと省略記法

そうです。複雑怪奇なJoinファンクタは省略記法で書かれていたのでした。省略せずに書くと

structure PrologParser =
Join(struct
     structure LrParser = LrParser
     structure ParserData = PrologParserLrVals.ParserData
     structure Lex = PrologLex
     end)
functor Join(X: sig
             structure Lex : LEXER
             structure ParserData: PARSER_DATA
             structure LrParser : LR_PARSER
             sharing ParserData.LrTable = LrParser.LrTable
             sharing ParserData.Token = LrParser.Token
             sharing type Lex.UserDeclarations.svalue = ParserData.svalue
             sharing type Lex.UserDeclarations.pos = ParserData.pos
             sharing type Lex.UserDeclarations.token = ParserData.Token.token
             end)
                 : PARSER =
...

となります。形式的には引数のモジュールは1つでありながら事実上複数のモジュールを渡していたのです。structureが付いていたのはモジュール内モジュールだったから、sharingはモジュール内モジュールに対する制約宣言です。

なぜこれでハマったかというとSML#のインターフェースファイルでは省略記法が使えなかったからです。地雷の数だけ強くなれるよ♪

参考

Written by κeen