LoginSignup
2
2

More than 3 years have passed since last update.

再帰的なmodule type

Last updated at Posted at 2020-12-05

この記事はML Advent Calendar 2020 6日目の記事です。


OCamlの定義にはデフォルトで再帰的なものと非再帰的なものがある。

型やクラスの定義はデフォルトが再帰で、値の定義やモジュール、モジュール型の定義は非再帰だ。

型の定義で再帰をしたくない場合は type nonrec と書き(OCaml 4.02.2以降)、値の定義で再帰したい場合は let rec と書く。モジュールは module rec と書く。ではモジュール型の場合は?

そもそもそんなもの必要なのかという気もするが、第一級モジュールでオブジェクトみたいなことをしはじめると出てこなくもない。

たとえば、非破壊的な、2次元平面上の点を考える。

レコードで書くとこんな感じだろう。

type t = { x : int; y : int }

let move ?(dx = 0) ?(dy = 0) {x; y} =
  { x = x + dx; y = y + dy }

クラスにするとこう。

class point = fun ~x ~y ->
  object (self)
    method x = x
    method y = y

    method move ?(dx = 0) ?(dy = 0) () =
      new point ~x:(self#x + dx) ~y:(self#y + dy)
  end
(*
class point :
  x:int ->
  y:int ->
  object
    method move : ?dx:int -> ?dy:int -> unit -> point
    method x : int
    method y : int
  end
*)

move メソッドの戻り値型は自分自身なので、再帰が必要になってしまった。

第一級モジュールで書こうとすると、 module type rec のようなものはないのでどうしたものかというところだけど、実は再帰モジュールを使うとどうにかなる。

module rec Point : sig
  module type S = sig
    val x : int
    val y : int

    val move : ?dx:int -> ?dy:int -> unit -> (module Point.S)
  end
end = Point

let rec make_point ~x ~y : (module Point.S) =
  (module struct
    let x = x
    let y = y

    let move ?(dx = 0) ?(dy = 0) () =
      make_point ~x:(x + dx) ~y:(y + dy)
  end)

モジュール型自体では再帰せず、外側のモジュールを再帰させる。やりたいことはシグネチャレベルで完結しているので、モジュール定義の右辺では単に自分自身を参照する。

これで問題(?)は解決したのだけれど、OCamlのissuesを眺めると再帰モジュール関連のものが結構あり、使っていて大丈夫なんだっけという気持ちがよぎる。module type recを直接サポートしようというissueもあるがはたして。

補足

クラスをつかった定義では move の戻り値を point にしたが、後々の拡張を考えると(するかどうかは別として)、 self type を使っておいた方がよい。こうしておくと、サブクラスでも move の定義がそのまま使える。

class point = fun ~x ~y ->
  object (_ : 'self)
    val x = x
    val y = y
    method x = x
    method y = y

    method move ?(dx = 0) ?(dy = 0) () : 'self =
      {< x = x + dx; y = y + dy >}
  end
(*
class point :                                                                                       
  x:int ->                                                                                          
  y:int ->                                                                                          
  object ('a)                                                                                       
    val x : int                                                                                     
    val y : int                                                                                     
    method move : ?dx:int -> ?dy:int -> unit -> 'a                                                  
    method x : int                                                                                  
    method y : int                                                                                  
  end                                                                                               
*)
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2