Annotated で型に Refine(lambda ...)
を付けます。最初の引数は制約する値です。複数の述語はすべて満たす必要があります。
from refinepy import Refine
from typing import Annotated
Nat = Annotated[int, Refine(lambda v: v >= 0)]
def increment(x: Nat) -> Annotated[int, Refine(lambda result, x: result == x + 1)]:
return x + 1from collections.abc import Sequence
def at(
xs: Sequence[int],
i: Annotated[int, Refine(lambda v, xs: 0 <= v < len(xs))],
) -> int:
return xs[i]追加の引数は関数入口の入力を指定します。引数の契約では先行する引数、戻り値の契約では全引数を参照できます。本体で再代入しても、契約内の入口の値は変わりません。
型エイリアスとローカル宣言の述語は引数を1つ取ります。count: Nat = 0
などのローカル契約は後の代入にも適用します。引数の契約は入口だけを制約します。関数の引数と戻り値には型が必要です。ローカルの型は推論できます。
Literal は値を、同じ基本型の
int、bool、str、None
の非空のリテラル集合に制限します。符号付き整数、静的な型エイリアス、引用した型注釈に対応します。
from refinepy import Refine
from typing import Literal
Mode = Literal["read", "write"]
def switch(mode: Mode) -> Mode:
return "write" if mode == "read" else "read"
def reverse_pair(pair: tuple[Literal[0, 1], Mode]) -> tuple[Mode, Literal[0, 1]]:
return pair[::-1]基本型の混在、計算した値や名前、enum、入れ子の Literal
は未対応です。True は Literal[1]
を満たしますが、整数の 1 は Literal[True]
を満たしません。既定値は定義時に基本型、使用時に細分化の条件を検査します。
固定長タプルは構築、リテラル添字、連結、等価比較、非ゼロの静的ステップによるスライスに対応します。境界は静的な値、省略、None に限ります。空スライスや結果が既知の比較でも、全フィールドを評価して検査します。等価比較は再帰的で、リストのフィールドには比較可能な一致する要素型が必要です。マッピングと抽象シーケンスのフィールドの等価比較は未対応です。長さや無関係なスカラー型が異なる場合は不等になります。
順序は、互換性のある整数・bool、文字列、入れ子のタプル、対応する None のフィールドを辞書式に比較し、同じ接頭辞なら長さで決まります。共通フィールドが省略可能型やコンテナーなら拒否します。入れ子の値を含められますが、別名は残ります。更新したリストをタプルに格納したり、フィールドからリストを取り出したりしても、書き込み可能な所有権は得られません。
@requires(lambda ...)
は、後に宣言した引数も含めて入口の入力を制約します。複数の事前条件はすべて満たす必要があります。
from refinepy import requires
@requires(lambda xs, i: 0 <= i < len(xs))
def read_at(xs: Sequence[int], i: int) -> int:
return xs[i]requires、ensures、decreases
は、通常の必須位置引数を持つリテラル lambda
を、位置引数として1つ取ります。インポート、別名、再エクスポートに対応します。同名のユーザー関数は対象外です。@reflect
に括弧は付けません。reflect と decreases
の重複はエラーです。デコレーターの順序は影響しません。定義より前に、TYPE_CHECKING
の外でインポートする必要があります。他のデコレーターと旧式の関数コメント指示は未対応です。
呼び出しは引数の契約と事前条件を満たす必要があります。ソース内の呼び出し先を検証してから、その戻り値の契約を使います。デコレーターは実行時に述語を評価したり契約を強制したりしません。検証器は対象コードをインポートしません。
@ensures
は、必須の戻り値の基本型に述語を加えます。最初の引数は結果、残りは不変な入口の入力です。-> None
では結果の引数を省き、lambda: proposition も使えます。
from refinepy import ensures
@ensures(lambda result, n: result == n + 1)
def increment(n: int) -> int:
return n + 1
@ensures(lambda n: n + 0 == n)
def identity_lemma(n: int) -> None:
return None複数の ensures と戻り値の Refine
はすべて満たす必要があります。各述語は独立に安全に評価できる必要があり、自分や他の述語の定義可能性を正当化できません。定義を取り込む関数には細分化のない戻り値が必要で、ensures
は使えません。
直接再帰する関数には @decreases(lambda ...)
を1つ付けます。引数は入口の入力です。尺度は整数、または通常の整数からなる非空の平坦なリテラルタプルです。各自己呼び出しで定義可能性と厳密な減少を示し、現在と次の全成分を非負に保ちます。スカラーなら
0 <= next < entry
です。タプルは辞書式に比較し、先行成分が減れば後続成分は増やせます。基底ケースの尺度は非負でなくても構いません。bool
の成分は拒否します。
ループ直前の decreases(lambda ...)
は、現在束縛されているヘッダーのローカル値を使います。通常の反復と
continue
では、最終反復も含め、次の尺度が定義可能、非負、かつ厳密に小さいことを示します。break、return、反復0回では減少は不要です。入れ子の各ループにも尺度が必要です。未束縛の
for
ターゲットや内部の反復カウンターは引数に指定できません。
呼び出し先も独立に停止する必要があります。減少は呼び出し結果の契約を使わずに検査するため、結果に依存する減少は未証明になる場合があります。信頼された外部モデルや相互再帰は停止性の根拠にできません。停止性を要求した場合、到達不能なループも含めて全ループに尺度が必要です。
verified
だけでは正常に戻る場合の契約を保証します。停止性には対象関数とその呼び出し先の証拠が必要です。Python
の再帰深度や利用可能な資源は保証しません。
Assert
は、その位置で命題を検査し、後の代入には契約を課しません。対象は確実に束縛済みの関数ローカル変数です。引用しない注釈に初期値を付けず、Assert
をちょうど1つ指定します。
from typing import Annotated
from refinepy import Assert
def absolute(n: int) -> int:
x = abs(n)
x: Annotated[int, Assert(lambda value: value >= 0)]
return x基本型は現在の値と一致する必要があり、既存の細分化も検査します。lambda の最初の引数は現在の値、追加引数は現在束縛済みのローカル変数です。自由変数としてのローカル参照、既定値、可変長引数は未対応です。後の再代入で確立済みの事実は更新されません。
Assert(lambda v: P(v), using=lambda arg: lemma(arg))
は、戻り値が細分化された None
のソース補題を直接1回呼び出します。引数と事前条件を検査し、補題の全呼び出し先の停止性を独立に証明する必要があります。外部モデル、ループ、相互再帰、循環するアサーションの証拠は静的証明に使えません。using
を省くと、現在の事実と受け入れ済みの定義から命題を証明します。Python
はこのローカル注釈を評価しません。引数、戻り値、エイリアス、入れ子の型、モジュール注釈には
Assert を使えません。
@reflect
で検査済みの定義を述語から呼び出せます。入力と結果は細分化のない対応型にし、入れ子の細分化、事前条件、ensures
は使えません。代入、平坦なアンパック、アサーション、分岐、return
に対応します。ループとコンテナーへの書き込みは未対応です。使わない計算も含め、元の本体と全引数の評価が安全である必要があります。
補題は、戻り値の契約に命題を記述した検査対象関数です。Annotated[None, Refine(lambda _, x: ...)]
や -> None と @ensures
を使います。本体で命題を証明し、呼び出すと正常に戻る場合の契約を使えます。通常の契約だけでは、関数を論理式から呼び出せません。ガイドの例を参照してください。
定義を取り込む関数は、宣言名で自身を呼び出せます。入力と結果は細分化のない対応型、本体はループなし、事前条件なしとし、整数の
decreases
尺度を明示します。等式を使う前に本体の安全性と減少を検証し、他の呼び出し先も検証と停止性が必要です。
展開はスカラーだけのシグネチャで8層、コンテナーを含む場合は3層までです。残りの呼び出しは未解釈のままなので、偽の等式も含めて
unknown
になる場合があります。展開の制限で受け入れや証明が止まることもあります。相互再帰、別名での自己呼び出し、複合要素を持つコンテナーを含む再帰シグネチャは対象外です。有界展開を超える等式には、検査済みの帰納補題を使います。
戻り値が細分化された None
の直接再帰関数は、整数または辞書式の decreases
尺度で命題を帰納的に証明できます。各自己呼び出しは契約と厳密な減少を満たす必要があり、宣言した戻り値の命題を局所的な帰納仮説として使います。基底ケースと再帰ケースの全
return
で命題を証明します。減少は呼び出し結果の契約から独立に検査します。依存先は検証と停止性、ループは尺度が必要です。定義の取り込みは別途受け入れられる必要があります。
自動帰納探索と相互帰納は未対応です。尺度なしの補題は、正常に戻る場合の部分正当性だけを保証します。
要素の契約はすべての有効な要素、外側の契約はコンテナーを制約します。読み取りには範囲やキーの存在の証明が必要です。シーケンスの負の添字と、T | None・Optional[T]
の不在検査や型の絞り込みに対応します。
NonEmptyNats = Annotated[Sequence[Nat], Refine(lambda xs: len(xs) > 0)]
def first(xs: NonEmptyNats) -> Nat:
return xs[0]from collections.abc import Mapping
def port_or_zero(ports: Mapping[str, Nat]) -> Nat:
port = ports.get("http")
if port is None:
return 0
return port新しい共有されていないローカルリストは、単独の
append・extend、+=、整数添字による代入で更新できます。要素とリスト全体の細分化も検査します。入力・共有リスト、反復中のリストへの書き込み、pop、スライス代入、辞書への書き込みは未対応です。
新しいリストを返す検査済みのソースヘルパーは、1つのローカル名に書き込み可能な所有権を移せます。独立に確保され、共有のない可変コンテナーのタプルを返すヘルパーなら、left, right = split(xs)
のような直接の平坦なアンパックでリストの所有権を移せます。借用・共有フィールド、先にタプルを保存する操作、入れ子のアンパックでは所有権を得られません。内容が等しい契約だけでは不十分で、全依存先と戻り値の全選択肢の検証が必要です。抽象
Sequence と省略可能な戻り値は所有権を移しません。
要素には対応するタプル、省略可能な値、入れ子のコンテナーを使えます。外側のリストを所有しても、取り出した内側のリストには書き込めません。更新した別のローカルリストを要素として格納すると拒否します。リストの等価比較は、細分化が異なっても、再帰的に比較可能な一致するスカラー、省略可能型、タプル、リストの要素型に対応します。抽象 Sequence とマッピングの要素は比較できません。連結には一致する要素契約が必要で、書き込み可能なコンテナー型は不変です。ガイドに例があります。
推論で必要な性質を示せない場合は、ループの直前に
invariant(lambda ...)
を置きます。引数は現在のヘッダーのローカル値です。停止性を要求するには
decreases(lambda ...) を1つ加えます。
from refinepy import invariant, decreases
def count_to(n: Nat) -> Annotated[int, Refine(lambda result, n: result == n)]:
i = 0
invariant(lambda i, n: 0 <= i <= n)
decreases(lambda i, n: n - i)
while i < n:
i += 1
return i各マーカーは必須引数を持つリテラル lambda を位置引数として1つ取ります。同じブロックで連続するマーカーは次のループに適用されます。間にコメントや空行は置けますが、他の文は置けません。インポート、別名、再エクスポートに対応します。実行時に述語は評価しません。旧式の不変条件コメントは受け付けますが非推奨です。
不変条件は開始時と各反復後に検査します。assert
も証明義務であり、対応する述語の構文を使う必要があります。
構文が対応範囲内でも、すべての性質を証明できるとは限りません。未対応の構文、ソルバーの不確実性、解析やモデル復元の上限到達を検証成功として扱いません。
| 項目 | 対応する書き方 | 未対応または制限される形式 |
|---|---|---|
| スカラー型 | int、bool、str、None |
float、complex、bytes、Any、object、ユーザー定義クラスは不可 |
| 省略可能型とタプル型 | T \| None、Optional[T]、対応する値型のフィールドからなる固定長タプル、単一基底型の
Literal 契約 |
一般の共用体、可変長タプル注釈は不可 |
| コンテナー型 | Sequence[T]、list[T]、Mapping[str, T]、dict[str, T]。T
は対応する値型で、細分化も可 |
集合、任意のマッピングキー、独自コンテナー実装、取り出した入れ子の別名参照を介した書き込みは不可 |
| 算術 | +、-、*(a * b、a * a
を含む)、比較、0以外と証明した整数の除数による // と
% |
/、累乗、ビット演算は不可。変数の除数や非線形の証明義務は
unknown になる場合あり |
| 条件 | if / elif /
else、比較の連鎖、and /
or、条件式、is None による絞り込み |
オペランド型には対応する共通型が必要。None、in、not in
を含む比較の連鎖は、個別の真偽式に分ける必要あり。マッピングの真偽検査や任意の同一性検査は不可 |
| 文字列 | 内容の等価・非等価比較、辞書式の大小比較、len、連結、安全な整数の添字アクセス、本体と述語でのステップ1のスライス |
その他のスライスステップ、走査、f-string、文字列メソッドは不可 |
| シーケンス操作 | 長さ、整数の添字アクセス、安定した走査、リストの等価比較・連結、本体と述語の0以外の定数ステップによるリストスライス | 等価比較には再帰的に比較可能な要素ソートの一致が必要。連結には要素契約の一致も必要。抽象
Sequence の等価比較、抽象 Sequence
のスライス、所属検査、入力・共有リストの変更は不可 |
| 固定長タプル操作 | 構築、長さ、リテラル添字、等価・非等価比較、辞書式の大小比較、本体と述語の連結・静的スライス | 大小比較には比較可能な共通要素が必要。動的な積のスライス境界は不可 |
| マッピング操作 | 文字列キーの所属、参照、get(key[, default]) |
マッピングの長さ、走査、keys / values /
items、更新は不可 |
| 代入 | ローカル再束縛、連鎖代入、平坦な固定長タプルのアンパック、整数の累算代入、ローカル文字列の
+=、排他的なローカルリストの append/extend/+=
と整数添字への書き込み |
入力・共有リストへの書き込み、スライス・属性への書き込み、入れ子・スター付きのアンパック、del、global、nonlocal
は不可 |
| 反復 | while、モデル化したシーケンス・直接の
range・直接の enumerate の
for、ループの
else、break、continue |
一般のイテレーター値、zip、iter、next、ジェネレーター、非同期反復は不可 |
| リスト内包表記 | 同期の for
1つ、名前の対象、モデル化したシーケンスまたは同種の固定長タプルに対する、純粋な対応値型の要素式 |
入れ子の構築と内包表記は対応。フィルター、1つの内包表記内の複数ジェネレーター、要素式での定義を取り込まないソース呼び出しは不可 |
| 関数 | トップレベルの注釈付き関数、固定した位置・キーワード引数、/、キーワード専用引数、スカラー・None
リテラルまたは int / bool / str
リテラルの平坦なタプルによる既定値、宣言した契約付きの直接再帰 |
入れ子のタプルや None
を含むタプルの既定値は不可。相互再帰、クロージャー、その他のデコレーター、関数値、可変長引数、引数展開は不可 |
| ジェネリック | 制約付きスカラー引数1つ:T: (int, str) または
T: (bool, str)。TypeVar でも記述可 |
重なりのある int / bool
の組、制約なし・上限のみのジェネリック、複数の型引数、ジェネリックな別名は不可 |
| その他の文 | return、pass、証明のアサーション |
match、raise、try、with、yield、非同期関数は不可 |
述語は bool
を返し、宣言型と確立済みの事実の下で安全に評価できる必要があります。len(xs) > 0 and xs[0] >= 0
のように短絡ガードで読み取りを正当化できます。
| 式 | 許可する使い方 |
|---|---|
| リテラル、lambda の束縛名、不変のスカラー定数 | 任意のグローバル変数の読み取りや代入は不可 |
| 整数算術と比較 | 変数同士の乗算、除算・剰余を許可。除数が0でないことは、先行する短絡ガードや入口の事実から導く必要あり |
and、or、not、条件式 |
対応する真偽変換と互換性のある値型。最終的な述語は bool |
len(value) |
文字列、モデル化したシーケンス、固定長タプル。マッピングは不可 |
xs[i]、mapping[key]、key in mapping |
読み取り前に境界やキーの存在を証明。固定長タプルのフィールド添字はリテラル |
mapping.get(key[, default]) |
文字列キーと互換性のあるスカラーまたは省略可能型の既定値 |
abs(x)、min(x, y)、max(x, y) |
整数引数。min / max
は位置引数をちょうど2つ取る |
リストリテラル、等価比較、連結、定数ステップのスライスを使えます。空リストには文脈からの要素型が必要です。タプルの構築、連結、静的スライスも使えますが、全フィールドの安全性を検査します。定義を取り込まないユーザー関数の呼び出し、明示的な量化、マッピング構築、内包表記は述語内では使えません。sum、all、any、sorted、isinstance、スカラー変換、第三者
API には既定のモデルがありません。
検証済みの契約は、入口の契約を満たす入力とモデル化した組み込み実行環境に適用します。モデル化した実行時エラーと正常に戻る場合の契約を検査し、停止性は別の証拠で示します。Python
の注釈は契約を強制しません。ブラウザーは単一モジュールを Python
を実行せずに読みます。パッケージ解決と外部モデル登録はネイティブのワークフローで扱います。ページ移動には
#/playground、#/guide、#/reference
と見出しへのリンクを使えます。サンプルは
#/playground/<sample>
で指定し、bounds、fibonacci、squareRoot、series、rounding、division
を使えます。
要確認は
unproved、unknown、unsupported、失敗は
counterexample、error
をまとめたものです。個別の状態も表示します。レポート全体に診断がある場合は「すべて検証済み」にはなりません。状態の意味と停止性の要約はガイドを参照してください。