Haskell 、Erlang の次のブームは Coq に違いありません。とりあえず基本ということで、Coq でひねりのない brainfuck インタプリタを書いてみました。動作例。Coq のコードが色づけできないとは何事か。
Eval compute in (finite_execute " +++++++++[>++++++++>+++++++++++>+++++<<< -]>.>++.+++++++..+++.>-.------------.<++ ++++++.--------.+++.------.--------.>+. " "" 500).
= "Hello, world!"%string : string
残念ながら (?) Coq では停止性が保障された関数しか定義できません。ここでは最大評価ステップを指定しないといけないという仕様にしてごまかしています (引数の 500 がそれ) 。
Coq は定理証明器なので定理が証明できます。以下は 「Hello, world! のプログラムは Hello, world! を出力する」という定理。
Theorem Hello_world: finite_execute " +++++++++[>++++++++>+++++++++++>+++++<<< -]>.>++.+++++++..+++.>-.------------.<++ ++++++.--------.+++.------.--------.>+. " "" 500 = "Hello, world!"%string.
以下それの証明。不適切極まりない例ですね。Coq で FizzBuzz のパクリだし。
Proof. compute. reflexivity. Qed.
さらに、Coq で定義した関数や証明からプログラムを抽出することができます。
Extraction Language Haskell. Extraction "Brainfuck" init_thunk evaluate.
これで Brainfuck.hs が生成されます。Haskell は当然停止しない関数も定義できますので、ちょちょいとドライバ (生成されたコードを呼び出すコード) を書いてやることで Haskell による Brainfuck インタプリタが作れます。それはなんと Hello, world! のプログラムが正しく扱えることが証明済みのインタプリタなのです。ドライバ部分にバグがなければですが。
$ time runhugs Main.hs `cat hello.bf` Hello, world! real 0m0.610s user 0m0.380s sys 0m0.160s $ time runhugs Main.hs `cat quine.bf` > quine2.bf real 0m5.921s user 0m0.450s sys 0m5.400s $ diff quine.bf quine2.bf
以下コード。
Section Brainfuck. Require Import ZArith. Require Import List. Require Import Streams. Require Import Ascii. Require Import String. Open Scope Z_scope. Inductive Inst : Set := | i_incr : Inst | i_decr : Inst | i_right : Inst | i_left : Inst | i_getc : Inst | i_putc : Inst | i_open : Inst | i_close : Inst | i_exit : Inst. (** パーサ **) Section Parser. Fixpoint parse_aux (s : string) := match s with | EmptyString => nil | String "+" s' => i_incr :: parse_aux s' | String "-" s' => i_decr :: parse_aux s' | String ">" s' => i_right :: parse_aux s' | String "<" s' => i_left :: parse_aux s' | String "," s' => i_getc :: parse_aux s' | String "." s' => i_putc :: parse_aux s' | String "[" s' => i_open :: parse_aux s' | String "]" s' => i_close :: parse_aux s' | String _ s' => parse_aux s' end. Definition parse (s : string) : (list Inst * list Inst) := (nil, parse_aux s). End Parser. (** 評価器 **) Section Evaluator. (* 現在の命令を取り出す *) Definition fetch (c : (list Inst * list Inst)) : Inst := match c with | (_, i::_) => i | (_, nil) => i_exit end. (* 命令列の zipper を進める *) Definition next (c : (list Inst * list Inst)) : (list Inst * list Inst) := match c with | (l, i::r) => (i::l, r) | (l, nil) => (l, nil) end. (* 現在のセルに 1 を足す *) Definition incr (s : (Stream Z * Stream Z)) : (Stream Z * Stream Z) := match s with | (l, Cons x r) => (l, Cons (x + 1) r) end. (* 現在のセルから 1 を引く *) Definition decr (s : (Stream Z * Stream Z)) : (Stream Z * Stream Z) := match s with | (l, Cons x r) => (l, Cons (x - 1) r) end. (* ヘッダを右のセルへ動かす *) Definition right (s : (Stream Z * Stream Z)) : (Stream Z * Stream Z) := match s with | (l, Cons x r) => (Cons x l, r) end. (* ヘッダを左のセルへ動かす *) Definition left (s : (Stream Z * Stream Z)) : (Stream Z * Stream Z) := match s with | (Cons x l, r) => (l, Cons x r) end. (* 一文字読み込んで現在のセルに書き込む *) Definition getc (s : (Stream Z * Stream Z)) (i : string) : ((Stream Z * Stream Z) * string) := match s with | (l, Cons _ r) => match i with | EmptyString => ((l, Cons 0 r), EmptyString) | String c i' => ((l, Cons (Z_of_nat (nat_of_ascii c)) r), i') end end. (* 現在のセルの値を文字として出力する *) Definition putc (s : (Stream Z * Stream Z)) : ascii := match s with | (_, Cons c _) => ascii_of_nat (Zabs_nat c) end. Fixpoint open_aux (d : nat) (l : list Inst) (r : list Inst) { struct r } : (list Inst * list Inst) := match r with | (i_open :: r') => open_aux (S d) (i_open :: l) r' | (i_close :: r') => match d with | O => (i_close :: l, r') | S d' => open_aux d' (i_close :: l) r' end | (i :: r') => open_aux d (i :: l) r' | nil => (l, r) end. (* 現在の開き括弧に対応する閉じ括弧まで進む *) Definition open (c : (list Inst * list Inst)) (s : (Stream Z * Stream Z)) : (list Inst * list Inst) := match s with | (_, Cons 0 _) => match c with | (l, i_open :: r) => open_aux O (i_open :: l) r | _ => next c end | _ => next c end. Fixpoint close_aux (d : nat) (l : list Inst) (r : list Inst) { struct l } : (list Inst * list Inst) := match l with | (i_close :: l') => close_aux (S d) l' (i_close :: r) | (i_open :: l') => match d with | O => (l', i_open :: r) | S d' => close_aux d' l' (i_open :: r) end | (i :: l') => close_aux d l' (i :: r) | nil => (l, r) end. (* 現在の閉じ括弧に対応する開き括弧まで戻る *) Definition close (c : (list Inst * list Inst)) : (list Inst * list Inst) := match c with | (l, r) => close_aux O l r end. (* 一ステップ評価する *) Definition evaluate (thunk : ((list Inst * list Inst) * (Stream Z * Stream Z) * string)) := match thunk with | (code, state, input) => match fetch code with | i_incr => Some ((next code, incr state, input), None) | i_decr => Some ((next code, decr state, input), None) | i_right => Some ((next code, right state, input), None) | i_left => Some ((next code, left state, input), None) | i_getc => match getc state input with | (state', input') => Some ((next code, state', input'), None) end | i_putc => Some (((next code), state, input), Some (putc state)) | i_open => Some ((open code state, state, input), None) | i_close => Some ((close code, state, input), None) | i_exit => None end end. (* 評価を有限回繰り返す *) Fixpoint finite_loop (thunk : ((list Inst * list Inst) * (Stream Z * Stream Z) * string)) (count : nat) { struct count } : string := match count with | O => EmptyString | S count' => match evaluate thunk with | Some (thunk', Some out) => String out (finite_loop thunk' count') | Some (thunk', None) => finite_loop thunk' count' | None => EmptyString end end. End Evaluator. (* 初期状態のテープ *) CoFixpoint inf_tape : (Stream Z) := Cons 0 inf_tape. Definition init_state := (inf_tape, inf_tape). Definition init_thunk (code : string) (input : string) := (parse code, init_state, input). Definition finite_execute (code : string) (input : string) := finite_loop (init_thunk code input). (* Hello, world! を実行する *) Eval compute in (finite_execute ( "+++++++++[>++++++++>+++++++++++>+++++<<<" ++ "-]>.>++.+++++++..+++.>-.------------.<++" ++ "++++++.--------.+++.------.--------.>+." ) "" 500). (* Hello, world! のプログラムが Hello, world! を出力することを証明する *) Theorem Hello_world: finite_execute ( "+++++++++[>++++++++>+++++++++++>+++++<<<" ++ "-]>.>++.+++++++..+++.>-.------------.<++" ++ "++++++.--------.+++.------.--------.>+." ) "" 500 = "Hello, world!"%string. Proof. compute. reflexivity. Qed. (* echo に foo を入力すると foo を出力することを証明する *) Theorem Echo: finite_test "+[,[>+>+<<-]>[.[-]]>]" "foo" 5000 = "foo"%string. Proof. compute. reflexivity. Qed. End Brainfuck. (* Haskell のコードを出力する *) Extraction Language Haskell. Extraction "Brainfuck" init_thunk evaluate.
以下 Haskell のドライバ。
module Main where import Brainfuck import System import Char nat_of_int 0 = O nat_of_int n = S $ nat_of_int (n - 1) int_of_nat O = 0 int_of_nat (S n) = 1 + int_of_nat n ascii_of_char = ascii_of_nat . nat_of_int . ord coq_string_of_string [] = EmptyString coq_string_of_string (c : s) = String0 (ascii_of_char c) $ coq_string_of_string s char_of_ascii = chr . int_of_nat . nat_of_ascii string_of_coq_string EmptyString = [] string_of_coq_string (String0 a s) = char_of_ascii a : string_of_coq_string s mainloop = next . evaluate where next None = EmptyString next (Some (Pair thunk' None)) = mainloop thunk' next (Some (Pair thunk' (Some output))) = String0 output $ mainloop thunk' main :: IO () main = do args <- getArgs let args' = coq_string_of_string $ args !! 0 input <- getContents let input' = coq_string_of_string input let thunk = init_thunk args' input' putStrLn $ string_of_coq_string $ mainloop thunk
言うまでもないですが、Coq はこういうことをするものではないです (笑) 次はもうちょっと真面目なことをやります。次があったら。