
2026/04/07 21:16
# プロパティとは何か ## 定義 プロパティ(財産)とは、経済的価値を有し、所有・移転・賃貸が可能な有形・無形の資産を指します。これには不動産(土地及び建物)、個人の所持品、知的財産権、株式や債券といった金融資産などが含まれます。法的な文脈において、ある財産の所有権は、その資産を利用し、管理し、収益を得たり処分したりする権利を含む特定の実質的な権利を伴います。 ## プロパティの種類 - **不動産(Real Property)**:土地とそれに永続的に付帯する物(建築物、樹木、鉱山など)を指します。この種のプロパティは、政府による規制や区域制限法規の対象となります。 - **動産(Personal Property)**:土地に永続的に固定されていない移動可能な資産であり、自動車・家具・宝石・電子機器などが該当します。これらは容易に売却または譲渡することができます。 - **知的財産(Intellectual Property)**:特許権・商標権・著作権・営業秘密など、人類の精神的創作による無形のものを含みます。これらは法律で保護され、企業の競争優位性を支えています。 - **金融資産としてのプロパティ(Financial Properties)**:他の資産に対する所有権又は債権を示す株式、社債、投資信託、定期預金などの投資商品を含みます。 ## プロパティの主な特徴 1. **価値**:市場状況、立地、状態、需要などに影響されて決定的となる貨幣価値を有します。 2. **実用性**:居住や交通手段としての機能から、投資収入を生み出すまでの多様な実用的目的に寄与します。 3. **独占性**:法的一定の範囲内において、その財産の利用と利益をもたらす権利を排他的に享受する権能が所有権によって付与されます。 4. **移転可能性**:適用される法令に従って、原則として売買、相続、贈与の対象となり得ます。 ## 社会における重要性 プロパティは、融資のための担保提供、富の蓄積の実現、都市開発の促進、住宅所有を通じた生活の安定確保などを通じて経済活動において基盤的な役割を果たしています。また、地方自治体や国レベルの税収への寄与、住宅市場への影響、企業の事業拡大戦略への関与を通じて社会全体に広範な影響を与えています。
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
プロパティベーステスト(PBT)における核心的な課題は、相互依存するジェネレータを効果的に扱うことである。これは、後続のステップが先行する値に依存する値を指す。プロパティとは、すべての可能な入力に対して成り立たなければならない普遍量化された計算(例:
a -> Bool)であり、しばしば含意演算子(==>)を通じて前提条件が適用される。現在のフレームワークは、プロパティとジェネレータを組み合わせる際、その分離境界を損なうことなく対応することが困難である。いくつかの標準的なアプローチでは、ロジックをジェネレータ内部に埋め込み(「構造からの正当性」)、あるいは QuickCheck の forAll などの組合せ子を使用して生成とアサーションを混合させるが、これらは複雑で交互実行されるシナリオにおいて制限を受けることが多い。Hypothesis や Proptest のようなツールは、この分離に関する特定の設計決定を行っており、QuickCheck の抽象境界の Rust 移植版については、そのような相互に織り込まれた計算を十分にサポートしていないとの批判がなされている。Hegel の TestCase 引数などによる新たな概念は、このような必要な柔軟性を可能化することを試みている。本記述は、将来の PBT ライブラリには、ジェネレータとプロパティ間の自由な相互作用をサポートしつつ、抽象境界を損なうことはなく進化する必要があると結論づけている。この変化は、より高い表現力、安全性、そしてボイラープレートの削減を実現するために極めて重要であり、最終的には複雑で相互依存するデータシナリオに対して堅牢なテストツールを可能にするものである。この議論では、「Programmable Property-Based Testing」と「Database-Integrated Random Testing」に関連する研究工作についても言及されている。本文
2026 年 4 月 5 日投稿
所要時間:8 分
【カテゴリー】検証
「性質ベーステスト(Property-Based Testing, PBT)」を語る際、通常私たちは非常に抽象的な概念を用います。そこには「正しさを定義する性質(properties)」があり、「領域を定義する生成器(generators)」があり、PBT フレームワークはこれらの性質と生成器を組み合わせてバグを発見するための API を提供してくれます。すべて非常に素敵でシンプルです。
しかし、驚くべきことに、私の時間のかなりの部分を費やすのは、異なる PBT フレームワークを探索することであり、何度も既存の PBT ワークロードを別のフレームワークに移行させることです。これを行うためには、「PBT フレームワークとは何か」という抽象化を構築する必要があり、最初に述べた簡単な定義が PBT の本質を的確に捉えられていれば極めて容易だったはずです。残念ながらそうではありませんでした。では、なぜでしょうか。
性質とは、すべての可能な入に対して成り立つ普遍量化された計算です。プログラミング言語における性質の最も単純なモデルは、Boolean を返す関数です。例えば以下のようなもの:
property :: a -> Bool
\l -> reverse (reverse l) == l という例では、「リストを逆順にし続けても元のリストに戻ること」という性質が表現されています。ここで少し複雑になるのが「前置条件(preconditions)」です。これは入力が有効かどうかを示すルールです。以下のように記述できます:
data Database = ... execute :: Database -> Query -> Database query :: Database -> Query -> [[Value]] (==>) :: Bool -> Bool -> Maybe Bool (==>) precondition property = if precondition then Just property else Nothing prop_insert_select :: Database -> String -> [Value] -> Maybe Bool prop_insert_select db table values = let insert = Insert table values select = Select table "*" in hasTable db table ==> (values `elem` query (execute db insert) select)
ここで
(==>) は「推論演算子」を表しており、左辺(前置条件)が満たされない場合、性質の検証は行えないことを意味します。本例では、前置条件は hasTable db table であり、データベースに指定されたテーブルが存在するかをチェックしています。もし存在しない場合は、結果に関心を持たず、それを破棄します。一方、テーブルが存在すれば、INSERT を実行し、SELECT クエリによって挿入した値が正しく返されるか確認します。
性質を手に入れた段階で、それに対応するランダムな生成器も必要になります。全ての入力型に対して
Arbitrary インスタンスを定義しておけば、QuickCheck が残りの処理を引き受けてくれるはずです。
data Value = Number Int | String String | ... instance Arbitrary Value where arbitrary = oneof [Number <$> arbitrary, String <$> arbitrary, ...] instance Arbitrary Database where arbitrary = ...
しかし、実際にはそうはなりません。これらのインスタンスを定義したとき、QuickCheck がどのような動作を行うかご存じでしょうか?ランダムなデータベース、ランダムな文字列、ランダムな Value のリストを作成し、その後
prop_insert_select 関数を呼び出します。ここで疑問 arises:ランダムな文字列が、データベース内に実際に存在する有効なテーブル名である確率はどれくらいでしょうか?
我々が求めているのは「依存生成器(dependent generator)」であり、ある値が他の値に依存して決定されるようなものです:
-- ランダムにテーブルを生成 genTable :: Gen Table genTable = ... tableName :: Table -> String tableName = ... genValuesFor :: Database -> String -> Gen [Value] genValuesFor db table = ... gen :: Gen (Database, String, [Value]) gen = do -- 作成するテーブル数を決定 numTables <- choose (1, 10) -- ランダムにテーブルを生成 tables <- vectorOf numTables genTable -- エンプティデータベースを作成 let db0 = emptyDatabase -- 生成されたテーブルでデータベースを povoil(注:povulate の誤記と思われる) let db = foldl createTable db0 tables -- これで有効なテーブル名と値を生成可能 let tableNames = map tableName tables table <- elements tableNames values <- genValuesFor db table return (db, table, values)
これで、前置条件の失敗を心配する必要がなくなります。入力自体は構築過程で正当性を担保されているためです。テーブルは既にデータベース内に存在するリストから選択されるため、失敗するはずがありません。では、テストを実行するにはどうすればよいでしょうか?以下が概念的な API の例です:
quickCheck :: Gen t -> (t -> Maybe Bool) -> Int -> Gen (Maybe t) quickCheck gen property n = if n == 0 then pure Nothing else do -- 提供された生成器を使ってランダムな入力を生成 input <- gen -- 生成した入力を用いて性質をチェック case property input of -- テストがパスしたら、別の入力を生成 Just True -> quickCheck gen property (n - 1) -- テストが失敗したら、失敗した入力とともに戻す Just False -> pure (Just input) -- 前置条件を満たさない場合は、別の入力を生成 Nothing -> quickCheck gen property (n - 1)
これは実際の QuickCheck の動作とは異なります(例えば、縮小(shrinking)やテストランナーの詳細な機構は無視されています)。しかし、その詳細については別の投稿に譲り、ここでは異なる点に焦点を当てたいと考えています。重要なのは、「生成器が性質から独立していない」という事実です。公平を期せば、これは一般的な要件であり、単に面白い入力に出会うことを願ってデータをランダムにサンプリングするだけでは不十分で、テスト対象のシステムを意識する必要があります。しかし、ここにはより緊急の問題があります。生成器は、生成そのものとは関係のない計算を実行することになります。
foldl createTable ... の呼び出しは、生成されたテーブルをデータベースに加えるためにデータベース自体を用いて行われます。これは、「生成器がいくつかのランダムな決定を行いデータ型を構築する」という我々の通常のメンタルモデルとは対照的です。
本例では、Database を一から生成しすぎず、代わりに非常に簡単なバージョンを生成し、その独自の API を使って拡張するのが現実的なのです。さらに言えば、生成器がすでに
(db, table) のペアを返しているため、性質内で hasTable db table をチェックして正当性を検出する必要はなくなります。この特定の生成器では、データベースに常にテーブルが存在することが保証されているため、そのチェックを外しても構いません。他の生成器の場合も同様で、チェックを生成器内に追加し、生成器を全 함수型(total)から部分函数型(partial)に変更し、逆に性質を partial から total に変更することも可能です。以下に例を示します:この場合、生成器は Gen (Maybe ...) を返し、性質は Bool を返しますが、hasTable チェックが生成器内に移動しているためです。
gen :: Gen (Maybe (Database, String, [Value])) gen = do db <- arbitrary :: Gen Database table <- arbitrary :: Gen String if hasTable db table then do values <- arbitrary :: Gen [Value] return $ Just (db, table, values) else return Nothing prop_insert_select :: (Database, String, [Value]) -> Bool prop_insert_select (db, table, values) = let insert = Insert table values select = Select table "*" in values `elem` query (execute db insert) select
性質の一部を生成器に移動できるなら、残りの部分も同様ではないでしょうか?
gen :: Gen (Maybe Bool) gen = do db <- arbitrary :: Gen Database table <- arbitrary :: Gen String if hasTable db table then do values <- arbitrary :: Gen [Value] let insert = Insert table values select = Select table "*" in return $ Just (values `elem` query (execute db insert) select) else return Nothing
見てください。これで元の「性質のみ」バージョンに戻りましたが、今度は生成器の文脈にいます。したがって、二つの部分に分けずにインラインで生成変更を直接行うことが可能になり、ゼロから何かを書き直さなくて済みます。QuickCheck は既にこのスタイルの性質ベーステストをサポートしており、ユーザーが
Gen 下で性質を書くことを強要することなく実現できます:
prop_insert_select :: Property prop_insert_select = forAll arbitrary $ \db -> forAll arbitrary $ \table -> hasTable db table ==> forAll arbitrary $ \values -> let insert = Insert table values select = Select table "*" in values `elem` query (execute db insert) select
このスタイルでは、各
forAll 組み合わせ子は生成器を受け取り、生成された値にアクセスできる文脈を生み出します。生成器を使用しているという事実を隠しつつも、依存生成器を自然かつ手間なく記述可能になります。ここで、性質は Boolean を返す関数ではなく、生成と断言の両方を包摂する「テスト」として機能します。公平を期せば、私が述べた内容はすべて革新的ではありません。forAll は過去 26 年にわたって QuickCheck の一部であり、PBT を書いている全ての人は「性質ベーステストは完全に分離された性質と生成器ではなく、両者の組み合わせである」という事実を理解しています。
Hypothesis の導入部分からは実践的な観点から特に説得力のある表現がなされています:
「与えられた範囲内のあらゆる入に対してパスするはずのテストを書き、Hypothesis がランダムにどの入力を確認するかを選択させる」
本質的に、性質ベーステストは、テスト対象プログラムに関する普遍量化された文としての性質を活用しますが、多くの場合、その抽象境界を壊してしまうことなく利用することが困難です。特にライブラリを実装する際にこの点を考慮することは重要です。例えば、Haskell の QuickCheck の Rust 移植版である
quickcheck は、上記で述べた「不十分」という抽象境界を誤認しています。
quicktest では Testable インスタンスが期待され、その関数は fn result(&self, _: &mut Gen) -> TestResult という形式をとります。このインスタンスは以下の通り、最多 8 つの要素を持つタプルに対して実装されています:
impl<T: Testable, $($name: Arbitrary + Debug),*> Testable for fn($($name),*) -> T { #[allow(non_snake_case)] fn result(&self, g: &mut Gen) -> TestResult { let self_ = *self; let a: ($($name,)*) = Arbitrary::arbitrary(g); let ( $($name,)* ) = a.clone(); let mut r = safe(move || {self_($($name),*)}).result(g); if r.is_failure() { let mut a = a.shrink(); while let Some(t) = a.next() { let ($($name,)*) = t.clone(); let mut r_new = safe(move || {self_($($name),*)}).result(g); if r_new.is_failure() { { let ($(ref $name,)*) : ($($name,)*) = t; r_new.arguments = Some(debug_reprs(&[$($name),*])); } r = r_new; a = t.shrink() } } } r } }
let a: ($($name,)*) = Arbitrary::arbitrary(g); という行は、入力タプルに対して任意生成器を呼び出し、性質が結果を計算する際に使用します:let mut r = safe(move || {self_($($name),*)}).result(g);。この設計は、まさに上で指摘した「不十分」という境界に基づいており、計算と生成を交互に行う必要があることを前提としています。
Proptest は私の知る限り、同様の判断を下しています。QuickCheck との比較においては、私がそれほど影響のあるものとは感じない差異に触れつつ、この記事が焦点を当てている機能に関する言及は含まれていません。新たに導入された Hegel は、Haskell における元の機能を実現する別のメカニズムを通じて生成とテストケースを混合することを可能にする
tc: TestCase 引数を維持しています。
この記事は特定の結論へと導くことを意図したものではなく、一般に PBT ライブラリで表現すべき事項を探求することを目的としていました。読みやすくかつ刺激的だったこと、そしてご意見や異議があれば
[email protected] で議論できれば幸いです。
関連する私の研究作品:
- プログラム可能・性質ベーステスト(Programmable Property-Based Testing)
- データベース統合型ランダム検証(Database-Integrated Random Testing)