OCaml で Bag
google:OCaml で Bag で検索したら「もしかして: ocamldebug 」とか言われるかと思いましたが, Google はそこまで賢くありませんでした. というくだらない話はさておき, OCaml で Bag のようなデータ構造 (同一要素が区別される集合) が必要になったので作ってみました. 作ったといっても Set モジュールを使っただけの単純なものです.
# module StringBag = Set.Make(struct type t = string let compare x y = max_int lor compare x y end);; module StringBag : sig type elt = string (* 略 *) end # StringBag.elements (StringBag.add "to" (StringBag.add "die" (StringBag.add "or" (StringBag.add "not" (StringBag.add "to" (StringBag.add "die" StringBag.empty))))));; - : StringBag.elt list = ["die"; "die"; "not"; "or"; "to"; "to"]大きな Bag を想定していないなら単に List を使えばいいと思います.
(続) Seven trees
[註] この記事には解答はありませんが,Coq の証明が読める人にはヒントになってしまうかもしれません.[追記] 解答の公開が解禁されたそうなので遅ればせながら公開致します.Blassのものと同じ定義なのであまり参考にならないと思います.
id:kikx さんが Seven trees 問題の解答を Coq で証明されているようなのでこちらも確認してみました.
Inductive tree : Set := | Leaf | Node : tree -> tree -> tree. Inductive tree7 : Set := | Seven : tree -> tree -> tree -> tree -> tree -> tree -> tree -> tree7. Definition seven2one (ts:tree7) : tree := match ts with | Seven Leaf Leaf Leaf Leaf Leaf Leaf (Node (Node (Node (Node t1 t2) t3) t4) t5) => Node (Node (Node (Node (Node Leaf t1) t2) t3) t4) t5 | Seven Leaf Leaf Leaf Leaf Leaf Leaf t => t | Seven Leaf Leaf Leaf Leaf Leaf t1 t2 => Node (Node (Node (Node (Node t1 t2) Leaf) Leaf) Leaf) Leaf | Seven Leaf Leaf Leaf Leaf (Node t1 t2) t3 t4 => Node (Node (Node (Node Leaf t4) t3) t1) t2 | Seven t1 t2 t3 t4 t5 t6 t7 => Node (Node (Node (Node (Node (Node t7 t6) t5) t4) t3) t2) t1 end. Definition one2seven (t:tree) : tree7 := match t with | Node (Node (Node (Node (Node (Node t1 t2) t3) Leaf) Leaf) Leaf) Leaf => Seven Leaf Leaf Leaf Leaf Leaf (Node t1 t2) t3 | Node (Node (Node (Node (Node (Node t1 t2) t3) t4) t5) t6) t7 => Seven t7 t6 t5 t4 t3 t2 t1 | Node (Node (Node (Node (Node Leaf t1) t2) t3) t4) t5 => Seven Leaf Leaf Leaf Leaf Leaf Leaf (Node (Node (Node (Node t1 t2) t3) t4) t5) | Node (Node (Node (Node Leaf t1) t2) t3) t4 => Seven Leaf Leaf Leaf Leaf (Node t3 t4) t2 t1 | t => Seven Leaf Leaf Leaf Leaf Leaf Leaf t end. Theorem one2one : forall (t:tree), seven2one (one2seven t) = t. Proof. intro. destruct t as[|[|[|[|[|[|??]?][|??]][|??]][|??]][|?]];auto. Qed. Theorem seven2seven : forall (t:tree7), one2seven (seven2one t) = t. Proof. intro. destruct t as[[|][|?][|??][|??][|??][|??][|[|[|[|]]]]];auto. Qed.Coq の証明でまでゴルフしたくなるのはもう病気ですね.
Seven trees
NICTA に来てから tree transducer 漬けの毎日. こちらに来るまで,Macro tree transducer (MTT) とか Attributed tree transducer (ATT) とかそれより上のクラスばっかり相手にしていて, Top-down tree transducer (TDTT) は自明な世界だと思ってましたが, Partial や Nondeterministic を許すとなかなか奥が深いです.
と,余談はさておき,表題の件です. 元ネタを知っていたので狡いですが, ヒントは,二分木 (Tree) が の不動点で表現できることと, の複素数解 が を満たすということですね. あまり,ヒントになってないか…. OCaml で実装してみましたが,双方向とも 5 種類のパターンによる分岐で定義できます.
Pretty printers for tuples, variants, records, Sets and Maps.
10/4まで北京泊・10/5 京都泊・10/6 東京泊・10/7 機内泊・10/8からシドニー泊…という激しいスケジュールを経て NICTA を訪問中です. 最近,再び OCaml のコードをたくさん書く機会ができたので, 久しぶりに OCaml プログラミングに関するメモを公開します.
Ruby には p
という便利な関数 (メソッド) があってどんな値でも可視化できますが,
OCaml だと自分で書かなきゃいけなくて面倒です.
extlib に Std.print
という関数がありますが,これは実行時の値を出力する関数なので,
実行時に単なる組になってしまうレコードやバリアントではフィールドやコンストラクタの名前が失われてしまい,十分な可視化ができているとは言えません.このため,結局自分で書く必要があります.
今回紹介する print.ml はそれを補助するプログラムで,先日公開した Glid のソースにも使われています.
表示形式をわりと自由にカスタマイズできるので,
printf デバッグ専門の人はもちろん,
ocamldebug 使いにとっても (install_printer などを使えば) 役に立つと思います.
特に,
- レコードの一部のフィールドは自分で定義した関数で表示したい
- 一部の構築子の引数の出力を省略したい
- Set や Map を可視化したいが,毎回書くのが面倒だ
実際に print.ml について説明する前に,
pretty printer (以下 PP) の関数の型について触れておきます.
PP 関数は,単相型ごとに与えられ,型 t
に対して
val pp_t : Format.formatter -> t -> unitという型を持ちます. 第一引数に
Format.formatter
を取る理由は,
整形して出力できることに加え,
対話環境で #install_printer
に直接渡すことができるからです.
Format.printf
でのフォーマット中の "%a"
と組み合わせられるので更に強力です.
以下では,型 t
に対する PP 関数を pp_t
と書くことにします.
また,多相型 'a t
に対する PP は,
'a
の PP を受け取るので,val pp_t : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unitという型を持ちます.従って
int list
型に対する PP は
pp_list pp_int
で定義できるようになります.
順序が逆転してしまうのは型引数を先に書くという ML の流儀によるものです
(Haskell などは順序通りに書けます).
同様に,多相型 ('a,'b) t
に対応する PP は,val pp_t : (Format.formatter -> 'a -> unit) * (Format.formatter -> 'b -> unit) -> Format.formatter -> ('a,'b) t -> unitを持ちます.好みに応じてカリー化しても構いませんが, この形で使った方が
(int, string) t
型に対応する PP が
pp_t (pp_int, pp_string)
と書けるので,型とその PP がきれいに対応します.
さて,ここからは print.ml の内容と使い方の説明です.
print.ml はこちらで配布しています.
以下,(* print.ml ... *)
で始まる部分は,配布した print.ml 内にあるので,
使う側は記述する必要はありません.
(* 使い方 *)
で始まる部分を参考に,お好みの PP を作成してください.
(* 使い方 *)
の部分は,
ocamlc -c print.ml
でコンパイルして ocaml print.cmo
と対話環境を起動し,
open Print
している状態を想定しています.
(* print.ml (1) *) open Format let pp_unit fmt () = pp_print_string fmt "()" let pp_bool = pp_print_bool let pp_int = pp_print_int let pp_float = pp_print_float let pp_char fmt = fprintf fmt "%C" let pp_string fmt = fprintf fmt "%S"まず,Format モジュールを open してから,各基本型に対応する PP を用意しています. 使い方は,
(* 使い方 *) # Format.printf "x = %a@." pp_int 3;; x = 3 - : unit = () # Format.printf "s = %a@." pp_string "hoge";; s = "hoge" - : unit = ()という感じです.これらは,単独で使うことは少ないのであまり面白くない例ですが, 他の多相型に対する PP を作るときに便利なので導入しています.
次は,'a list
型に対応する PP です.
多相型なので要素の型に対応する PP 関数 pp_a
を受け取って,以下のように与えられています.
(* print.ml (2) *) let pp_list pp_a fmt xs = fprintf fmt "@[<1>["; begin match xs with | [] -> () | x::xs -> pp_a fmt x; List.iter (fprintf fmt ";@;%a" pp_a) xs end; fprintf fmt "]@]"
pp_list
は以下のようにして使います.(* 使い方 *) # let pp_int_list = pp_list pp_int;; val pp_int_list : Format.formatter -> int list -> unit = <fun> # Format.printf "List of integers : %a@." pp_int_list [1;2;3];; List of integers : [1; 2; 3] - : unit = () # Format.printf "List of lists of strings : %a@." (pp_list (pp_list pp_string)) [["This";"is";"a"];["nested";"list."];["The";"type";"is"];["string";"list";"list."]];; List of lists of strings : [["This"; "is"; "a"]; ["nested"; "list."]; ["The"; "type"; "is"]; ["string"; "list"; "list."]]
さて,ここからのソースは難解です.
10年以上 OCaml (OLabl) を触ってきた自分でも毎回解読に時間がかかります.
ただ,使う側からすればソースを理解する必要は全く無いので,
(* 使い方 *)
のコードだけ見て憶えれば十分です.
難解になっている理由は,一般的な型に対する PP の作成支援に必要なヘテロなリストが,
以前ここでも記事にした存在型の実装を利用しているためです.
(* print.ml (3) *) type pp_poly = { pp_poly: 'b. 'b pp_neg -> 'b } and 'b pp_neg = { pp_neg: 'a. (formatter -> 'a -> unit) -> 'a -> 'b } let pp_poly pp_a x = { pp_poly = fun k -> k.pp_neg pp_a x } let apply_pp_poly fmt p = p.pp_poly { pp_neg = fun pp_a -> pp_a fmt }型
pp_poly
は,∃α.(formatter -> α -> unit)
という型を表します.
この型をもつ値は,関数 pp_poly
に型 α
に対応する PP とその型の値を渡すことで作れます.
apply_pp_poly
で pp_poly
型の値を関数として使うことができますが,
print.ml を使うだけの人は憶えなくて構いません.
「関数 pp_poly
で PP に必要なヘテロなリストを作れる」ということだけ憶えれば十分です.
具体的な使い方については,後に示す組 (tuple) 型,ヴァリアント型,レコード型に対応する PP の作り方をご覧ください.
組型に対する PP は,任意個の任意型の値を扱うので,ヘテロなリストが必要になります.
print.ml では,
まず pp_poly
型のリストを ','
で接続された組型の形式で出力するための関数を定義しています.
(* print.ml (4) *) let pp_poly_list fmt = function | [] -> () | p::ps -> fprintf fmt "@[<1>(%a" apply_pp_poly p; List.iter (fprintf fmt ",@;%a" apply_pp_poly) ps; fprintf fmt ")@]"組型に対応する PP を作成するのにこれをそのまま使ってもよいですが, formatter を意識しないで PP を作成できるように
pp_tuple
を以下のように提供しています.(* print.ml (5) *) (* pp_tuple : ('a -> pp_poly list) -> formatter -> 'a -> unit *) let pp_tuple make_pps fmt x = pp_poly_list fmt (make_pps x)下のような感じで使います.
(* 使い方 *) # let pp_tuple_int_string_char = pp_tuple (fun (i,s,c) -> [pp_poly pp_int i; pp_poly pp_string s; pp_poly pp_char c]);; val pp_tuple_int_string_char : Format.formatter -> int * string * char -> unit = <fun> # Format.printf "Tuple of integer, string and character : %a@." pp_tuple_int_string_char (3,"hoge",'A');; Tuple of integer, string and character : (3, "hoge", 'A') - : unit = ()
pp_poly
型のリストを作るときは,各要素を関数 pp_poly
で作る必要がありますが,
これを List.map
等を使って簡略化することはできません.
理由については実際に試してみるとよくわかると思います.
次は,ヴァリアント型に対応する PP です.多相ヴァリアント型にも同様に使えます. [追記] 但し,拡張も縮小もできない多相ヴァリアント型に制限されているので,入力の型を明示する必要があります.
(* print.ml (6) *) (* pp_variant : ('a -> string * pp_poly list) -> Format.formatter -> 'a -> unit *) let pp_variant make_cps fmt x = let cname, ps = make_cps x in fprintf fmt "%s%a" cname pp_poly_list ps以下のように使います.
(* 使い方 *) # type color = Black | White | Gray of int | RGB of int * int * int;; type color = Black | White | Gray of int | RGB of int * int * int # let pp_color = pp_variant (function | Black -> "Black", [] | White -> "White", [] | Gray i -> "Gray", [pp_poly pp_int i] | RGB(r,g,b) -> "RGB", [pp_poly pp_int r; pp_poly pp_int g; pp_poly pp_int b]);; val pp_color : Format.formatter -> color -> unit = <fun> # Format.printf "Color is %a.@." pp_color (RGB(140, 182, 255));; Color is RGB(140,182,255). - : unit = ()型変数を含むヴァリアント型については, print.ml で与えられている option 型に対応する PP のコードが参考になると思います.
(* print.ml (7) *) let pp_option pp_a = pp_variant (function | None -> "None", [] | Some x -> "Some", [pp_poly pp_a x])
pp_list
と同様に,中身に対応する PP を渡すことで PP が作成できます.(* 使い方 *) # Format.printf "maybe = %a@." (pp_option pp_int) (Some(42));; maybe = Some(42) - : unit = ()残念ながら,面倒なので括弧の省略は全く考えていません. 再帰型の場合は,PP 関数も再帰で定義されます.
(* 使い方 *) # type 'a bin = Tip of 'a | Fork of 'a bin * 'a bin;; type 'a bin = Tip of 'a | Fork of 'a bin * 'a bin # let rec pp_bin pp_a = pp_variant (function | Tip x -> "Tip", [pp_poly pp_a x] | Fork(t1,t2) -> "Fork", [pp_poly (pp_bin pp_a) t1; pp_poly (pp_bin pp_a) t2]);; val pp_bin : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a bin -> unit = <fun> # Format.printf "Tree = %a@." (pp_bin pp_int) (Fork(Fork(Tip 1,Tip 2),Tip 3));; Tree = Fork(Fork(Tip(1), Tip(2)), Tip(3)) - : unit = ()実は,型引数を持たない再帰型の場合は,η 展開が必要です.
# type int_bin = IntTip of int | IntFork of int_bin * int_bin;; type bin = IntTip of int | IntFork of int_bin * int_bin # let rec pp_int_bin fmt = pp_variant (function | IntTip x -> "IntTip", [pp_poly pp_int x] | IntFork(t1,t2) -> "IntFork", [pp_poly pp_int_bin t1; pp_poly pp_int_bin t2]) fmt;;
pp_int_bin
に関しては η 展開が必要なかったので修正しました.
最後に,レコード型に対する PP の作成です.
(* print.ml (8) *) (* pp_record : ('a -> (string * pp_poly) list) -> Format.formatter -> 'a -> unit *) let pp_record make_pp_fields fmt x = let apply_pp_field fmt (f,p) = fprintf fmt "@[<2>%s = @,%a@]" f apply_pp_poly p in fprintf fmt "@[<1>{"; begin match make_pp_fields x with | [] -> () | fp::fps -> apply_pp_field fmt fp; List.iter (fprintf fmt ";@;%a" apply_pp_field) fps end; fprintf fmt "}@]"ヴァリアント型に対する PP と同様に,関数を受け取る形を取っています. [追記] フィールドごとに整形されるようにブロックを追加しました. これに合わせて配布中の print.ml も更新しています. 以下のように使います.
(* 使い方 *) # type polar = { radial: float; angle: float };; type polar = { radial : float; angle : float; } # let pp_polar = pp_record (fun x -> ["radial", pp_poly pp_float x.radial; "angle", pp_poly pp_float x.angle]);; val pp_polar : Format.formatter -> polar -> unit = <fun> # Format.printf "polar = %a@." pp_polar { radial = 1.05; angle = 3.14 };; polar = {radial = 1.05; angle = 3.14} - : unit = ()
配布している print.ml では, おまけとして Set.S.t や Map.S.t に対する PP の作成を支援するファンクタも用意しています.
(* print.ml (9) *) module Set = struct module Make(Ord:Set.OrderedType) : sig include Set.S with type elt = Ord.t val pp_t : (Format.formatter -> elt -> unit) -> Format.formatter -> t -> unit end = struct module S = Set.Make(Ord) include S let pp_t pp_elt fmt set = fprintf fmt "@[<1>{"; ignore (S.fold (fun elt is_fst -> if is_fst then pp_elt fmt elt else fprintf fmt ",@;%a" pp_elt elt; false) set true); fprintf fmt "}@]" end end module Map = struct module Make(Ord:Map.OrderedType) : sig include Map.S with type key = Ord.t val pp_t : (Format.formatter -> key -> unit) -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit end = struct module M = Map.Make(Ord) include M let pp_t pp_key pp_a fmt map = fprintf fmt "@[<1>{"; let pp_each key fmt v = fprintf fmt "@[<2>%a => @,%a@]" pp_key key pp_a v in ignore (M.fold (fun key v is_fst -> if is_fst then pp_each key fmt v else fprintf fmt ";@;%a" (pp_each key) v; false) map true); fprintf fmt "}@]" end endprint.ml をコンパイルすれば,以下のように使うことができます.
(* 使い方 *) # module IntSet = Set.Make (struct type t = int let compare = compare end);; ...(出力は省略)... # let pp_int_set = IntSet.pp_t pp_int;; val pp_int_set : Format.formatter -> IntSet.t -> unit = <fun> # Format.printf "IntSet = %a@." pp_int_set (IntSet.add 1 (IntSet.add 2 (IntSet.singleton 3)));; IntSet = {1, 2, 3} - : unit = () # module StrMap = Map.Make (struct type t = string let compare = compare end);; ...(出力は省略)... # let pp_str_int_map = StrMap.pp_t pp_string pp_int;; val pp_str_int_map : Format.formatter -> int StrMap.t -> unit = <fun> # Format.printf "int StrMap = %a@." pp_str_int_map (StrMap.add "hoge" 1 (StrMap.add "fuga" 2 StrMap.empty));; int StrMap = {"fuga" => 2; "hoge" => 1} - : unit = ()要するに,
open
してから Set.Make
や Map.Make
を普段通り使うだけです.
あとは,そのモジュールの pp_t
を利用すれば PP を作成できます.
長くなりましたが,OCaml プログラマならこれくらいは (たぶん) 各自で用意しているような気がします. 各型に対する PP を毎度毎度書くのが面倒なので,自分の中では結構活用しています.
連続する整数をまとめる
http://d.hatena.ne.jp/higepon/20080925/1222326246
(via http://www.atdot.net/~ko1/diary/200809.html#d27)
(via http://shinh.skr.jp/m/?date=20080927#p01)
a=[1,3,4,5,6,12,13,15] a.map{|e|x,y=$*[-1];e-1==(y||x)?$*[-1]=[x,e]:$*<<e};p$*
割と読みやすいかも.まだ縮みそう.
Glid: Grass/Let interpreter and decompiler
草言語Grassが流行っているようなので,Grassプログラミング支援ツールGlidを公開します. Objective Camlで実装されているという点ではYTさんに先を越されてしまいましたが, より多くの機能を提供しています. といっても,実装の効率はあまりよくないので,速いGrass処理系が欲しいだけの方にはあまり役に立ちません. Glidは,簡単にいうと言語Grassと言語Letの間の双方向の翻訳ツールです. 言語Letは,次の文法で与えられる単純な関数型言語でGrassよりは楽にプログラムが書けます.
Prog := Def*
Def := let Var Var* = Exp
Exp := Var | Exp Exp | let Var = Exp in Exp | Exp;Exp
Var := In | Out | Succ | W | [_a-zA-Z0-9]+
主な使用目的は,
- Grassを書く代わりに,Letでプログラムを書く.
- 他人の書いたGrassを読む.
言語Letでは以下のようなプログラムが書けます.
(* echo.let : An echo program in Let *) let f x = Out(In _); x x
変数_
は,使用しない変数を表しGrassでは最も近い変数への参照に変換されます.
Grassと同様に,最後に定義した変数が自己適用されるので,
このプログラムは入力をそのまま出力するループになります.
今回提供するプログラムをglid
とすると以下のコマンドで実行できます.
$ glid echo.letまた,このプログラムからGrassのコードへ変換するには,
$ glid echo.let -o echo.grassとするだけです.
-o echo.grass
の代わりに-
とすれば標準出力に出力されます.
なお,Grassコードは glid echo.grass
で実行できます.
さて,GlidではGrassコードからLetプログラムへの変換も可能です. たとえば,先ほど得られた Grass の echo プログラム
wWWWWWwWWWwWWWwwwは,
$ glid echo.grass -o echo2.letなどと実行すればLetプログラムへ翻訳できます.ちなみに,得られたプログラムは
let _ x1 = Out (In x1); x1 x1となります. また,変数名に数字も使えるので,以下のようなプログラムが書けます.
(* hello.let: Print "Hello, world!" *) let one f x = Succ(f(f x)) let zero f x = f(f x) let 1 x f = f (one x) let 0 x f = f (zero x) let 0b1 f = f Succ let putchar x = Out(x(one(zero(zero(one(zero(zero(zero Succ))))))W)) let H = 0b1 0 0 1 0 0 0 putchar let e = 0b1 1 0 0 1 0 1 putchar let l = 0b1 1 0 1 1 0 0 putchar let l = 0b1 1 0 1 1 0 0 putchar let o = 0b1 1 0 1 1 1 1 putchar let comma = 0b1 0 1 1 0 0 putchar let space = 0b1 0 0 0 0 0 putchar let w = 0b1 1 1 0 1 1 1 putchar let o = 0b1 1 0 1 1 1 1 putchar let r = 0b1 1 1 0 0 1 0 putchar let l = 0b1 1 0 1 1 0 0 putchar let d = 0b1 1 0 0 1 0 0 putchar let bang = 0b1 0 0 0 0 1 putchar let nl = 0b1 0 1 0 putchar
ゴルフ的には無駄だらけです,念のため. ちなみに,このプログラムをGrassに変換してからLetに戻すとこんな感じ.
let f1 x1 x2 = Succ (x1 (x1 x2)) let f2 x1 x2 = x1 (x1 x2) let f3 x1 x2 = x2 (f1 x1) let f4 x1 x2 = x2 (f2 x1) let f5 x1 = x1 Succ let f6 x1 = Out (x1 (f1 (f2 (f2 (f1 (f2 (f2 (f2 Succ)))))) W)) let _ = f5 f4 f4 f3 f4 f4 f4 f6 let _ = f5 f3 f4 f4 f3 f4 f3 f6 let _ = f5 f3 f4 f3 f3 f4 f4 f6 let _ = f5 f3 f4 f3 f3 f4 f4 f6 let _ = f5 f3 f4 f3 f3 f3 f3 f6 let _ = f5 f4 f3 f3 f4 f4 f6 let _ = f5 f4 f4 f4 f4 f4 f6 let _ = f5 f3 f3 f4 f3 f3 f3 f6 let _ = f5 f3 f4 f3 f3 f3 f3 f6 let _ = f5 f3 f3 f4 f4 f3 f4 f6 let _ = f5 f3 f4 f3 f3 f4 f4 f6 let _ = f5 f3 f4 f4 f3 f4 f4 f6 let _ = f5 f4 f4 f4 f4 f3 f6 let _ = f5 f4 f3 f4 f6
そんなに読みやすくないのは,元のプログラムにも問題があるんだと思います. ちなみに,Glidでは,GlassからLetへ変換してそれをGrassに戻しても 束縛の順序等が変更されるため同じになるとは限りませんが, おそらく冪等性は成り立つはずです.
その他,構文エラーについては行や桁の表示がされるのは少し便利かも. ダウンロードはこちらからどうぞ. [追記 (08/09/19)] 密かにバージョンアップ. ビルドには,Objective Caml (>=3.08.4) が必要です.