プログラミング
プログラミング言語におけるいくつかの優れたアイデア
A Few Good Ideas in Programming Languages (prydt.xyz)
要約
この記事では、プログラミング言語における3つの優れたアイデア、フロータイピング、借用チェック、契約プログラミングについて解説しています。フロータイピングは、変数が異なる型を保持できる柔軟性を提供し、コンパイラが型推論とチェックを行います。Rustの借用チェックは、メモリ安全性を保証し、データ競合を防ぎます。D言語の契約プログラミングは、関数の事前条件・事後条件やクラスの不変条件を記述する構文を提供し、コードの堅牢性を高めます。
全文翻訳
プログラミング言語におけるいくつかの優れたアイデア
投稿日: 2026-05-10 著者: Pranoy Dutta
私が気に入っているプログラミング言語の機能はほんのいくつかですが、ここに挙げます。
1. フロータイピング
私が最初にCrystalで出会った素晴らしいアイデアは、フロータイピングというものです。Crystalは、Rubyプログラミング言語に非常に似た構文を持つ、静的型チェックを備えたコンパイル型プログラミング言語です。Crystalが動的型付け言語のように感じられる理由の一つは、私が使ったほとんどの静的型付け言語とは異なり、変数がそのライフタイム中に複数の型に代入できることです。以下に例を示します。
my_var = 5 # ここでのmy_varの型はInt32です
assert(my_var.is_a?(Int32))
if some_complex_condition()
my_var = "hello!" # ここでのmy_varの型はStringです
assert(my_var.is_a?(String))
end
# ここでのmy_varの型は何でしょうか?
# Int32 | String です
# assert(my_var.is_a?(Int32 | String))
これについて最も興味深いのは、my_varが単なるInt32である時期があり、Stringであることが保証される領域があり、そしてコンパイラがどちらとも保証できない領域があるということです…そのため、その型はすべての可能性の和集合になります。もしここでmy_varに対してStringメソッドを実行しようとすると、それはStringではなくInt32 | Stringであるため失敗します。コンパイラは、if my_var.is_a?(String) のようなチェックを追加するように強制し、可能な型をStringのみに絞り込みます。これは、コンパイル型言語に実行時のペナルティをほとんど払わずに、動的な感覚を与えるための高度な型推論の素晴らしい例です。TypeScriptにもフロータイピングと型絞り込みがあります!
2. 借用チェック
Rustは、ガベージコレクタなしでメモリ安全性を保証するシステムプログラミング言語です。並行プログラムにおけるメモリ安全性のバグの大きなクラスの一つは、忌まわしいデータ競合です。これは、複数のスレッドが同期なしに同じメモリ位置を同時に読み書きする状況です。借用チェッカーは、Rustがコンパイル時にデータ競合を静的に防止できる方法です。これは以下のルールを強制します。
- 任意の借用は、所有者のスコープを超えてはならない
- 次のいずれかを持つことができる:厳密に1つのミュータブルな参照(&mut T)または1つ以上のイミュータブルな参照(&T)
これは、多くのリーダーまたは単一のライターを許可するロックであるリーダーライターロックを思い出させるかもしれません。なぜなら、データ競合を防ぐためには、読み取りと書き込みを同期させるだけで十分だからです。並行同期の目的は、特定のメモリ位置への書き込みをシリアル化することです。私は借用チェッカーを気に入っています。なぜなら、データ競合の問題に対する非常にエレガントな解決策であり、コンパイル時のチェックと追加の複雑さというコストのみで得られるゼロコスト抽象化だからです。追加の複雑さは実際のトレードオフですが、並行プログラムを書いているのであれば、この複雑さは主題の本質的なものです。
3. 契約プログラミング
もしあなたが多くのプログラミングをしてきたなら、おそらく謙虚なassert文に出くわしたことがあるでしょう。これは、特定の不変条件が満たされていない場合にエラーを報告するために使用されます。すべてのプログラムには、満たす必要がある不変条件があります。「この日付は常にその日付より後である」とか「このツリーは常にバランスが取れている」といったことです。Dプログラミング言語(非常に過小評価されている言語です)は、関数レベルまたはオブジェクトレベルでより複雑な不変条件を記述するための構文サポートを提供する契約プログラミングをサポートしています。Dには標準のassert文があります。
double always_positive = 100;
assert(always_positive > 0);
しかし、Dにはsemanticsの違いを示すために使用されるenforceもあります。Assertはプログラムの不変条件違反に使用されます。assertがトリガーされた場合、これはプログラムの正確性バグを示すはずです。enforceは代わりに、外部の問題による例外をスローします。ユーザー入力の範囲外や環境の問題のようなものです。
enforce(length >= 7, "Must be at least 7.");
さらに、Dは関数の事前条件と事後条件のための構文サポートを持っています。以下はProgramming in Dから取られた例です。
int daysInFebruary(int year)
out (result) {
assert((result == 28) || (result == 29));
}
do {
return isLeapYear(year) ? 29 : 28;
}
ここで、daysInFebruary関数は、常に28または29のみを返すという事後条件を持っています。それ以外は明らかにその関数における論理エラーです。そして最後に、Dにはオブジェクトデータが常に一貫していることを保証するために使用されるクラスレベルの不変条件があります。すべてをまとめた、少し複雑な例を以下に示します。
class BankAccount {
private double balance;
invariant() {
balance >= 0; // オブジェクトレベルの不変条件、常にチェックされる
}
this(double initialBalance)
in (initialBalance >= 0, "Initial balance cannot be negative") {
balance = initialBalance;
}
void deposit(double amount)
in (amount > 0, "Deposit amount must be positive")
out (; balance == balance + amount) // メソッド返却後にチェックされる
{
balance += amount;
}
void withdraw(double amount)
in (amount > 0, "Withdrawal amount must be positive")
in (amount <= balance, "Insufficient funds")
out (; balance >= 0, "Balance must remain non-negative")
{
balance -= amount;
}
double getBalance()
out (result; result >= 0, "Balance returned must be non-negative")
{
return balance;
}
}
このinvariant()ブロックは、すべてのクラスメソッドの最初と最後に一貫性チェック関数を実行するよりもはるかにクリーンで保守しやすく、慣用的な意味合いを持っています。
-- Pranoy