Regions
💡 お知らせ: このドキュメントはAIによって翻訳されています。表現に違和感がある場合は、原文(英語)を参照するか、翻訳にご協力ください。
Flix は スコープ付き のミュータブルなメモリをサポートしています。Flix では、すべてのミュータブルなメモリは、そのレキシカルスコープに紐づく リージョン(region) に属します。実行がリージョンのレキシカルスコープを抜けると、そのリージョン内のすべてのメモリは到達不能になります。
リージョンが役立つのは、内部で ミューテーション(mutation) を使う 純粋関数 を実装できるようにするからです。この強力なアイデアを、いくつかの実例で説明していきますが、まずはリージョンの使い方から見ていきましょう。
新しいリージョンスコープは region 構文で導入します:
region rc { // リージョン開始。
... // リージョンハンドル `rc` がスコープ内にあります。
} // リージョンが終了し、`rc` に紐づくすべてのデータがスコープ外になります。
リージョンを使えば、内部でミューテーションを使いながらも純粋な sort 関数を実装できます:
def sort(l: List[a]): List[a] with Order[a] =
region rc {
let arr = List.toArray(rc, l);
Array.sort(arr);
Array.toList(arr)
}
ここでは rc という名前のリージョンを導入しています。List.toArray 関数を使って、リスト l をリージョン rc に紐づくミュータブルな配列 arr に変換します。次に、効率的なインプレースソートアルゴリズムを使う Array.sort で arr をソートします。最後に、ソート済みの配列をリストに戻して返します。sort 関数は内部でミューテーションを使っているにもかかわらず、純粋です。
別の例として、List[a] 向けの toString 関数を実装してみましょう。これは純粋ですが、内部ではミュータブルな StringBuilder を使っています:
def toString(l: List[a]): String with ToString[a] =
region rc {
let sb = StringBuilder.empty(rc);
List.forEach(x -> StringBuilder.appendString("${x} :: ", sb), l);
StringBuilder.appendString("Nil", sb);
StringBuilder.toString(sb)
} // rc のスコープが終了し、式全体は純粋です。
プログラミングのパターンは同じです。新しいリージョンを開き、リージョン内に StringBuilder を確保し、ビルダーを文字列で埋め、最後に1つの文字列に変換します。
リージョンを使うと、特定の 関数型の操作 をより効率的に実装できます。たとえば、次は List.flatMap の高速な実装です:
def flatMap(f: a -> List[b] \ ef, l: List[a]): List[b] \ ef =
region rc {
let ml = MutList.empty(rc);
l |> List.forEach(x -> MutList.append(f(x), ml));
MutList.toList(ml)
}
リージョンは値である
リージョン(またはリージョンハンドル)は、関数の引数として渡せる 値 です。これは、たとえばミュータブルなデータ構造を確保して返す再利用可能な関数を書きたいときに便利です。
たとえば、次は List.toMutDeque 関数です:
def toMutDeque(rc: Region[r], l: List[a]): MutDeque[a, r] \ r =
let d = MutDeque.empty(rc);
foreach (x <- l) {
MutDeque.pushBack(x, d)
};
d
この関数はリージョンハンドル rc を受け取り、与えられたリージョン内に新しいミュータブルな両端キュー(deque、MutDeque)を確保し、リスト l のすべての要素を deque に挿入して、それを返します。
リージョンはスコープを持つ
リージョンと、それに紐づくすべてのメモリは、そのレキシカルスコープより長く生存することはできません。
次のプログラムを考えてみましょう:
def main(): Unit \ IO =
let escaped = region rc {
Array#{1, 2, 3} @ rc
};
println(escaped)
ここでは Array#{1, 2, 3} をリージョン rc 内に確保し、それを囲んでいるスコープの外へ返そうとしています。Flix コンパイラはこのようなエスケープ違反を検出し、エラーを報告します:
❌ -- Type Error ----------------------------
>> The region variable 'rc' escapes its scope.
2 |> let escaped = region rc {
3 |> Array#{1, 2, 3} @ rc
4 |> };
region variable escapes.