IID.systems
プロフィール事業紹介形式手法AIアライメントエッセイ書籍プログラミング教室GitHubEnglish
English

VDMを深掘りする

ソートの仕様を例に — 「どう計算するか」ではなく「何が得られれば良いか」を書く形式的仕様記述

VDMとは

VDM(Vienna Development Method)は、1970年代のIBMウィーン研究所におけるプログラミング言語意味論の研究に起源を持つ、最も歴史の長いモデル指向形式手法のひとつです。仕様記述言語 VDM-SL は ISO 標準(ISO/IEC 13817-1:1996)として規格化されており、オブジェクト指向拡張の VDM++、分散・リアルタイム拡張の VDM-RT へと発展しています。

VDMの実践上の強みは、仕様を「実行できる」形でも書けることです。数学的に厳密でありながら、実行可能なスタイル(後述の陽仕様)で書いた仕様はインタプリタで動かして挙動を確かめられるため(仕様アニメーション)、仕様そのものの誤りを実装前に発見できます。日本でも、ICカードファームウェア開発への大規模適用などの実績があります。

代表的なツール:Overture(統合開発環境)、VDMJ(コマンドライン処理系)、ViennaTalk


例題:ソートを「仕様」として書く

「リストを昇順に並べ替える」— 誰もが知るこの処理を、形式的な仕様として書くとどうなるでしょうか。重要なのは、ソートの仕様はマージソートかクイックソートかというアルゴリズムには一切言及しない、ということです。仕様が定義すべきなのは「入力に対して、どんな出力が得られれば良いのか」— それだけです。

VDMでは、この「何が得られれば良いか」を事後条件(postcondition)として書きます。本体(アルゴリズム)を持たず、事前・事後条件だけで関数を定義するこの書き方は陰仕様(implicit specification)と呼ばれ、VDMによる仕様記述の基本形です。

Sort (l : seq of int) r : seq of int post IsPermutation(r, l) and IsOrdered(r);

読み下すと「Sort は整数列 l を受け取り、整数列 r を返す。ただし r は l の並べ替え(順列)であり、かつ昇順に整列している」。これがソートの仕様のすべてです。なお事前条件(pre)は書かれていません — どんな整数列でも入力として受け付けるためです。

IsOrdered : seq of int -> bool IsOrdered(l) == forall i, j in set inds l & i > j => l(i) >= l(j); IsPermutation : seq of int * seq of int -> bool IsPermutation(l1, l2) == forall e in set (elems l1 union elems l2) & card {i | i in set inds l1 & l1(i) = e} = card {i | i in set inds l2 & l2(i) = e};

事後条件に現れる2つの述語も、VDMの関数として定義します。IsOrdered は「添字が後ろなら値は前の要素以上(昇順)」であること、IsPermutation は「どの値についても、2つの列での出現回数が一致する(同じ要素の並べ替えである)」ことを表します。

仕様の急所:順列条件を忘れると

もし事後条件が IsOrdered(r) だけだったら、「常に空リストを返す」「常に [1, 2, 3] を返す」ような実装まで仕様を満たしてしまいます。IsPermutation が「要素を勝手に捨てない・増やさない・書き換えない」ことを保証しているのです。何を書くかと同じくらい、何が欠けると壊れるかを考えることが、仕様記述の急所です。


陰仕様と陽仕様

VDMの関数には2つの書き方があります。事前・事後条件だけで「満たすべき性質」を定める陰仕様と、本体(計算手順)を与える陽仕様(explicit specification)です。開発ではまず陰仕様で性質を固定し、のちに陽仕様・実装へと段階的に詳細化していくのが基本の流れです。

観点陰仕様(implicit)陽仕様(explicit)
記述するもの満たすべき性質(事前・事後条件)計算の手順(アルゴリズム)
実行直接は実行できないインタプリタで実行できる(仕様アニメーション)
アルゴリズム決めない(実装者に委ねる)ひとつに決める
主な役割正しさの定義・契約プロトタイプ・実行可能モデル

また、陽仕様の形を取りながら本体を未定義のまま残す「is not yet specified」という書き方もあります。「この関数の中身はまだ決めない」ことを、型チェックが通る形で明示的に残せます。

Sort : seq of int -> seq of int Sort(l) == is not yet specified;

陰仕様は実行できません。しかしそれは欠陥ではなく本質です。アルゴリズムを決めていないからこそ、挿入ソートでもマージソートでもクイックソートでも、事後条件を満たす限りどんな実装も「正しい」と判定できる — 陰仕様は、特定の実装から独立した中立の判定基準として機能します。


陽仕様の例:1つの仕様、3つの実装

Overtureプロジェクトの公式サンプル「sort」(Peter Gorm Larsen, 2010)には、同じソート仕様に対する複数の陽仕様が収められています。どれも実行可能で、そしてどれも冒頭の陰仕様の事後条件を満たします。

① 仕様をほぼそのまま実行する:順列から選ぶ

Sort : seq of int -> seq of int Sort(l) == let r in set Permutations(l) be st IsOrdered(r) in r;

「l の順列のうち、整列しているものを選んで返す」— 事後条件の構造をそのまま写し取った陽仕様です。let ... be st ...(〜を満たすものをひとつ選ぶ)という非決定的選択で書かれています(Permutations は l のすべての順列の集合を返す補助関数 — 定義は省略)。順列は n! 通りあるため実用性能はありませんが、仕様と実装の中間形がどんな姿かを教えてくれます。

② 挿入ソート

DoSorting : seq of int -> seq of int DoSorting(l) == if l = [] then [] else InsertSorted(hd l, DoSorting(tl l)); InsertSorted : int * seq of int -> seq of int InsertSorted(i, l) == cases true : (l = []) -> [i], (i <= hd l) -> [i] ^ l, others -> [hd l] ^ InsertSorted(i, tl l) end;

「残りをソートした結果に、先頭要素を正しい位置へ挿入する」という再帰的な定義です。

③ マージソート

MergeSorter : seq of int -> seq of int MergeSorter(l) == cases l : [], [-] -> l, others -> let l1 ^ l2 in set {l} be st abs (len l1 - len l2) < 2 in Merge(MergeSorter(l1), MergeSorter(l2)) end; Merge : seq of int * seq of int -> seq of int Merge(l1, l2) == cases mk_(l1, l2) : mk_([], l), mk_(l, []) -> l, others -> if hd l1 <= hd l2 then [hd l1] ^ Merge(tl l1, l2) else [hd l2] ^ Merge(l1, tl l2) end;

リストをほぼ半分に分け、それぞれをソートして併合する、分割統治の定義です。ここでも let ... be st ... が「長さの差が2未満になる分割を選ぶ」という形で現れます。

実行してみる

陽仕様は VDMJ / Overture のインタプリタでそのまま実行できます。実装を書く前に、仕様の段階で挙動を確かめられる — これが仕様アニメーションです。

> print DoSorting([3, 1, 66, 11, 5, 3, 99]) = [1, 3, 3, 5, 11, 66, 99]

順列選択・挿入ソート・マージソート — 3つの陽仕様はすべて、同じ陰仕様の事後条件を満たします。つまり陰仕様は「正しいソートとは何か」の定義であり、どのアルゴリズムを選ぶかは性能や文脈の問題であって、正しさの問題ではありません。この「何を」と「どう」の分離こそが、形式的仕様記述の本質です。


検証への接続

事後条件は単なるドキュメントではなく、検証の装置です。VDMでは陽仕様にも同じ事前・事後条件を併記でき、VDMJ は実行時にそれらを自動チェックするため、陽仕様(や仕様から書き起こした実装)が性質に違反すれば、その場で検出されます。

さらに「この陽仕様は事後条件を本当に満たすか」という問いは、証明責務(Proof Obligation)として機械的に生成し、証明することができます。実行やテストが「試した入力について正しい」ことしか示せないのに対し、証明は「すべての入力について正しい」ことを保証します。

「アルゴリズムに立ち入らず、入出力の契約だけを固定する」という陰仕様の考え方は、複数のAIエージェントが協調するマルチエージェント開発の契約定義にそのまま応用できます。IIDの formal-agent-contracts プラグインは、まさにこのスタイルでエージェント間契約を VDM-SL で記述・検証します。


本ページのコード例は、Overture プロジェクト公式サンプル集の VDM++ モデル「sort」(Peter Gorm Larsen, 2010)を、解説用に VDM-SL スタイルへ書き改めたものです(クラス構造と measure 節は省略)。

出典:Overture Examples — sort (VDM++)