4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

多相バリアントでopen recursion

Last updated at Posted at 2021-03-10

OCamlでも(オブジェクトもrefも使わずに)Open Recursionしたい!!・・・したい?? - Arantium Maestum に刺激を受けたので、多相バリアントでopen recursionする方法をメモ。

問題設定としては、よくある感じの整数の和と積がある言語の評価器を拡張可能にする。

type t =
  | Lit of int
  | Add of t * t
  | Mul of t * t

let rec eval = function
  | Lit i -> i
  | Add (l, r) -> eval l + eval r
  | Mul (l, r) -> eval l * eval r

まずは個々のバリアントタグを多相バリアントにばらして型を定義し、それぞれの評価関数を定義する。

リテラルに関しては何も考えずに定義するだけ。

type lit = [`Lit of int]

let eval_lit = function
  | `Lit i -> i

加算については、型定義で自分自身を参照すると再帰が閉じてしまうため、再帰部分は型パラメータで穴にしておく。うっかり再帰しないように nonrec をつけておいてみる。

type nonrec 't add = [`Add of 't * 't]

評価関数も同様に、 rec は付けず再帰的に部分木をたどる関数を外から与えるようにする。

let eval_add eval_rec = function
  | `Add (l, r) -> eval_rec l + eval_rec r

乗算も可算と同様に。

type nonrec 't mul = [`Mul of 't * 't]

let eval_mul eval_rec = function
  | `Mul (l, r) -> eval_rec l * eval_rec r

最後に let rec で再帰を閉じる。

type t = [ lit | t add | t mul ]

let rec eval : t -> int = function
  | `Mul _ as m -> eval_mul eval m
  | `Add _ as a -> eval_add eval a
  | `Lit _ as l -> eval_lit l

減算や除算を追加する場合もここまでと同様にすればよい。

ちなみに、この eval の型注釈は必要ない(というか、他の多相バリアント関連の型定義もすべて必要ない)。型注釈をつけなくても ([< `Add of 'a * 'a | `Lit of int | `Mul of 'a * 'a] as 'a) -> int のような型が推論される。

動かしてみる。

module M = struct
  let ( * ) x y = `Mul (x, y)
  let ( + ) x y = `Add (x, y)
  let i n = `Lit n
end

let () =
  Printf.printf "%d\n" @@ eval M.(i 3 + i 42 * i 5)
(* -| 213 *)

参考文献

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?