
2026/05/18 6:15
プロログによるコーディング・ホラー。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本記事は、純粋で単調なコーディング慣行への厳格な遵守が、堅牢な Prolog プログラムにとって不可欠であるという主張を展開している。一般的な産業パターンへ偏离することは、言語の述語論的性質を破損させ、高価な欠陥をもたらす。
!/0、(->)/2、および var/1 などの非単調な構造は、意図された解の喪失か不適切な結果を生じる。assertz/1 および retract/1 でグローバルデータベースを改変することは、隠れた依存関係を創出し予期せぬ失敗を引き起こすため、状態は世界の改変を通じてではなく述語の引数を通じて伝達されるべきである。(is)/2、(=:=)/2、および比較演算子のような低水準のアリティム操作は、開発者に矛盾する述語論的および操作的意味を両立させるよう迫り、プログラムを理解しやすくし、学習・テスト・推論を行うことを難しくする。不純な出力操作もまた、解答を Prolog タームとして記号論的に考察することを阻止する。純粋で単調な Prolog 部分集合を採用し、dif/2 のような近代の述語論的ツール、if_/3 のようなメタ述語、およびクリーンなデータ構造を活用することで、開発者はパフォーマンスを維持しつつ一般性・柔軟性・厳格なテスト可能性を取り戻し、プロフェッショナル環境での利用を制限するレガシーの負担から Prolog を解放することができる。本文
「恐ろしい!恐ろしい!」と囁かれるような叫びを聞かされたように思えた。(ジョゼフ・コンラート『闇の心』より)
なぜ、あなたはここにいますか?
Prolog プログラマーとして、あなたには反骨の精神が備わっているに違いありません。多くの場合、それは現在の業界全体が直面している問題の解決方法から離れ、さらに先にあるものを追求するための力となります。
しかし、このページの目的は、その反骨の精神を盲目的に発揮することが高くつくコストを伴い、実質的な利益をもたらさないと示唆することです。
優れた Prolog コードを作成するには、少数の原則に則るだけで十分です。これらの原則を守らないと、プログラムがいくつかの根本的な欠陥を抱えることになります。
恐ろしさ一:解を失うこと
効率的かつ正常終了する Prolog プログラムでも、大きく分けて二種類の欠陥が生じ得ます:
- 誤った答えを返す。
- 意図した解の一部または全部を報告しない。
どちらのケースがより深刻でしょうか。考えてみてください!
まず、プログラムは第一種の欠陥のみを持っていると仮定してみましょう。それでも正しい結果だけを入手する手段はあるでしょうか?次に、プログラムは第二種の欠陥のみを持っていると仮定してみましょう。その場合でも、意図されていたすべての解を入手する方法は何でしょうか?
プログラムを第二種の方法で欠陥させる主な要因は、不純な(non-pure)かつ非単調(non-monotonic)な言語構成要素を使用することです。具体的な例としては
!/0(カット)、(->)/2(条件句)、var/1 などがあります。宣言的(declarative)な解決策としては、クリーンなデータ構造の使用、制約演算子 dif/2、そしてメタ述語 if_/3 の採用などが挙げられます。
恐ろしさ二:グローバル状態
初心者としては、Prolog のグローバルデータベースを修正したいという誘惑にかられることが多いものです。これはプログラム内に暗黙的な依存関係を発生させます。「暗黙的」というのは、プログラム内にそれらの依存関係を強制する仕組みが何もないことを意味します。例えば、もし意図と異なる順序で述語を使用すると、予期せず失敗するか、奇妙な結果を返すことがあります。
プログラムをこのように欠陥させる主な要因は、
assertz/1 や retract/1 などの述語を使用することです。宣言的な解決策としては、状態を伝播させるために述語の引数やセミコンテキスト記法(semicontext notation) を用いることです。
恐ろしさ三:不純な出力
初心者としての段階では、システムターミナルに答えを印刷し、トップレベルから報告させず、自分で出力することに誘惑されることがあります。例えば、以下のようなコードを含めることがあります:
solve :- solution(S), format("the solution is: ~q\n", [S]).
このアプローチの重大な欠点は、そのような出力はシステムターミナル上でのみ生じ、プログラム内で Prolog 項としてアクセスできないため、容易にその出力について推論ができなくなることです。結果として、そうした出力に対するテストケースを作成しなくなる傾向があり、それが関連する述語を破壊する変更を引き起こす可能性を高めます。また、もう一つの深刻な短所は、このアプローチがコードを真の関係子(relation)として使用できないことを意味することにあります。
関係子の全般的な汎用性を活かすためには、Prolog コードを用いて解を記述し、出力の表示はトップレベルに任せるべきです:
solution(S) :- constraint_1(S), etc.
特別なフォーマットが必要となる場合があります。その場合は、依然として純粋な方法で出力を記述することができます。例えば、非項(nonterminal)
format_//2 を使用することで実現可能です。これにより、テストケースの作成が容易になります。
恐ろしさ四:低レベルの言語構成要素
一部の Prolog プログラマーは、より新しい言語構成要素の使用意義を見出しません。例えば、CLP(FD) の制約演算子は導入されてからわずか約 20 年しか経過しておらず、Prolog の歴史の中で比較的新しい開発です。低レベルの構成要素があなたをよく機能させてきたなら、なぜ最新の素材を学ぶ必要があるのでしょうか。数千万人の学生が低レベルの構成要素によって適切な教育を受けられなかった事実は、あなたの関心事とは限りません。
しかし、低レベルの構成要素に固執することには高い代償がかかります:言語を教えるのが困難になり、学ぶのが難しくなり、理解するのも不必要に複雑になります。学生は宣言的(semantics)と演算的(semantics)な意味論を実質的に同時に学ぶ必要があり、ほぼすべてのケースにおいてこれが多すぎます。
Prolog を不必要に教えにくくさせる主な要因は、初歩者を算術に関する低レベル述語
is/2、=:=/2、>/2 などの導入です。宣言的な解決策としては、制約演算子(CLP(FD) など)を教えることです。詳細は 宣言的整数算術 を参照してください。
ホラー・ファクトリアル
これらの欠陥の例を見るため、「ホラー・ファクトリアル」をご覧ください:
horror_factorial(0, 1) :- !. horror_factorial(N, F) :- N > 0, N1 is N - 1, horror_factorial(N1, F1), F is N*F1.
最も一般的な問い合わせに対して実行すると、解を失うホラーに気づくでしょう:
?- horror_factorial(N, F). % N = 0, F = 1. (唯一つの解)
!/0 を含まないバージョンもほぼ同様に恐ろしいものです:
horror_factorial(0, 1). horror_factorial(N, F) :- N > 0, N1 is N - 1, horror_factorial(N1, F1), F is N*F1.
低レベルの言語構成要素によるホラーが蔓延しています:
?- horror_factorial(N, F). % N = 0, F = 1 ; % caught: error(instantiation_error,'(is)'/2)
これを受容するならば、あなたは以下のような状態にあることになります:
- 陳腐な言語構成要素に制限されている。
- 関係子(relation)を関数(function)と誤認している。
- 最も一般的な問い合わせを顧みない。
- 不純な構成要素を使用することで宣言的デバッグを妨げている。
解決策:純粋性
ホラーを止めるには、Prolog の純粋で単調なサブセットに留まることが重要です。
小さく始めましょう。例えば、低レベルの整数算術の代わりに、より宣言的な代替手段を使用してください:
horror_factorial(0, 1) :- !. horror_factorial(N, F) :- N #> 0, N1 #= N - 1, horror_factorial(N1, F1), F #= N*F1.
「なにもないよりはマシ」です。そして、
!/0 を削除しましょう:
n_factorial(0, 1). n_factorial(N, F) :- N #> 0, N1 #= N - 1, n_factorial(N1, F1), F #= N*F1.
このバージョンは、最も一般的な問い合わせに対しても機能します:
?- n_factorial(N, F). % N = 0, F = 1 ; % N = 1, F = 1 ; % N = 2, F = 2 ; % N = 3, F = 6 ; % ... (無数の解が存在)
これは非常に良い結果です:数回の単純な変更で、極めて一般的なロジック・プログラムが誕生しました。
結論
まとめ而言いますと、私は、反骨の精神を発揮すべき場面で限定的にそれを発揮することを推奨します。
陳腐な機能に固執することは、方向性が間違えた反乱です。人生は過去へと逆戻りするものではなく、昨日に留まることもありませんから。
Prolog プログラムにおいて宣言的構成要素を使用することで、パフォーマンスを許容範囲内に保ちながら、より汎用的なプログラムを作り上げてください。