『On Lisp』の非決定性が面白い。
(define (two-numbers) (list (choose '(0 1 2 3 4 5)) (choose '(0 1 2 3 4 5)))) (define (parlor-trick sum) (let ((nums (two-numbers))) (if (= (apply + nums) sum) `(the sum of ,@nums) (fail)))) > (parlor-trick 7) (THE SUM OF 2 5)On Lisp - 非決定性
choose関数が正しい選択を行うと仮定したコードになっている。カラクリは単純。choose関数に指定された数値分の継続を順番に実行しているだけ。継続の実行をfail関数で行うことでfail関数が呼ばれないパスが求めるべき解になる。
実は継続を使わなくても例外でできる。というわけで、D言語で実装してみる。
import std.stdio; // やり直し例外 class FailException : Exception { this(string message, string file = __FILE__, int line = __LINE__) { super(message, file, line); } } // 試行錯誤 void choose_bind(T)(T[] choices, void delegate(T) fn) { foreach (int choice; choices) { try { fn(choice); } catch (FailException e) { continue; } return; // 見つかった } fail(); // 見つからない } // やり直し void fail() { throw new FailException("not found"); } // firstsとsecondsのうち和がsumになる組み合わせを探す。 void parlor_trick(int sum, int[] firsts, int[] seconds) { choose_bind(firsts, (int first) { choose_bind(seconds, (int second) { // sumはfirstとsecondの和である。 if (sum == first + second) { writeln(sum, " := ", first, " + ", second); } else { fail(); } }); }); } void main() { writeln("<7>"); parlor_trick(7, [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]); writeln("<23>"); parlor_trick(23, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] ); writeln("<70>"); parlor_trick(70, [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]); }
うまく動いた。
<7> 7 := 2 + 5 <23> 23 := 8 + 15 <70> main2.FailException@nond.d(26): not found