プログラミング言語におけるいくつかの優れたアイデア
A Few Good Ideas in Programming Languages (prydt.xyz)
要約
この記事では、プログラミング言語における3つの優れたアイデア、フロータイピング、借用チェック、契約プログラミングについて解説しています。フロータイピングは、変数が異なる型を保持できる柔軟性を提供し、コンパイラが型推論とチェックを行います。Rustの借用チェックは、メモリ安全性を保証し、データ競合を防ぎます。D言語の契約プログラミングは、関数の事前条件・事後条件やクラスの不変条件を記述する構文を提供し、コードの堅牢性を高めます。
全文翻訳
prydt's site
プログラミング言語における優れたアイデア
投稿日: 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)
- 1つのミュータブル参照 (
これはリーダー・ライターロックに似ているかもしれません。リーダー・ライターロックは、多くのリーダーまたは単一のライターを許可するロックです。これは、データ競合を防ぐためには、書き込みと読み込みを同期させるだけで十分だからです。並行同期の目的は、特定のメモリ位置への書き込みをシリアライズすることです。
私は借用チェッカーを気に入っています。なぜなら、データ競合の問題に対する非常にエレガントな解決策であり、コンパイル時のチェックと追加の複雑さというコストのみで得られるゼロコスト抽象化だからです。追加の複雑さは実際のトレードオフですが、並行プログラムを書いているのであれば、この複雑さは主題の本質的なものです。
3. 契約プログラミング
もしあなたが多くのプログラミングをしてきたなら、おそらく素朴なアサート文に出くわしたことがあるでしょう。これは、特定の不変条件が満たされていない場合にエラーを報告するために使用されます。すべてのプログラムには、維持すべき不変条件があります。「この日付は常に別の日付より後である」とか、「このツリーは常にバランスが取れている」といったことです。
Dプログラミング言語(非常に過小評価されている言語です)は契約プログラミングをサポートしており、関数レベルまたはオブジェクトレベルでより複雑な不変条件を記述するための構文サポートを提供しています。
Dには標準のアサート文があります。
double always_positive = 100;
assert(always_positive > 0);
しかし、Dにはenforceもあります。これはセマンティクスの違いを示すために使用されます。アサートはプログラムの不変条件違反に使用されます。アサートがトリガーされた場合、これはプログラムの正しさに関するバグを示すはずです。一方、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