Skip to content

モジュールシステム仕様

本ドキュメントでは、YaoXiang プログラミング言語のモジュールシステム仕様を定義します。モジュール定義、インポート・エクスポート、スコープ規則を含みます。


第1章:モジュールの定義

1.1 モジュールの基礎

モジュールはファイルを境界とします。各 .yx ファイルが1つのモジュールになります。

yaoxiang
// ファイル名がモジュール名になります
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }

1.2 モジュールの命名規則

  • モジュール名はファイル名で決まります
  • ファイル拡張子 .yx はモジュール名に含めません
  • モジュール名は PascalCase を使用します

第2章:モジュールのインポート

2.1 インポート構文

Import       ::= 'use' ModuleRef ImportSpec?
ImportSpec   ::= ('{' ImportItems '}') ('as' AliasList)?
              |  'as' AliasList
ImportItems  ::= Identifier (',' Identifier)* ','?
AliasList    ::= Identifier (',' Identifier)*

2.2 インポート方式

構文説明
use path;モジュールをインポート、最後の部分でアクセスuse std.io; -> io.print
use path.{a, b};指定した項目をインポートuse std.io.{print}; -> print
use path as alias;インポートして名前変更use std.io as io; -> io.print
use path.{i1, i2} as a, b;指定した項目をインポートして名前変更use std.io.{print, read} as p, r; -> p, r

2.3 インポートの例

yaoxiang
// モジュール全体をインポート
use std.io
io.print("Hello")

// 指定した項目をインポート
use std.io.{print, read}
print("Hello")

// インポートして名前変更
use std.io as io_module
io_module.print("Hello")

// 指定した項目をインポートして名前変更
use std.io.{print, read} as p, r
p("Hello")

第3章:モジュールのエクスポート

3.1 pub キーワード

pub キーワードを使用してエクスポート項目を宣言します:

yaoxiang
// Math.yx
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }

// プライベート項目(エクスポートされない)
internal_value: Int = 42

3.2 エクスポート規則

  • デフォルトではすべての項目はプライベートです
  • pub で宣言された項目は他のモジュールからアクセスできます
  • プライベート項目は現在のモジュール内でのみアクセス可能です

3.3 pub 自動バインディング

pub で宣言された関数について、コンパイラは同一ファイル内で定義された型に自動的にバインディングします:

yaoxiang
// pub で宣言すると、コンパイラが自動的にバインディング
pub distance: (p1: Point, p2: Point) -> Float = {
    dx = p1.x - p2.x
    dy = p1.y - p2.y
    (dx * dx + dy * dy).sqrt()
}

// コンパイラの自動推論:
// 1. Point が現在のファイルで定義されている
// 2. 関数引数に Point が含まれている
// 3. Point.distance = distance[0] を実行

// 呼び出し
d = distance(p1, p2)           // 関数型
d2 = p1.distance(p2)           // OOP 糖衣構文

第4章:スコープ

4.1 モジュールスコープ

各モジュールは独自のスコープを持ち、モジュール内の項目はデフォルトで外部から可視ではありません。

4.2 ネストスコープ

yaoxiang
// ブロックスコープ
{
    x = 10
    // x はこのスコープ内で可視
}
// x はこのスコープ外では不可視

// 関数スコープ
add: (a: Int, b: Int) -> Int = {
    result = a + b
    return result
}
// result は関数の外では不可視

4.3 変数の宣言とシャドウイング

YaoXiang には let キーワードはありません。x = value は宣言か代入か?原則があります:

代入優先。 宣言は1回しかしません。代入は百回もあります。高頻度操作を最短パスで通します。

x = value:
  スコープチェーンを辿って x を外方向へ検索
    → mut x を見つけた          :代入、OK(&mut トークン経由)
    → x を見つけた(既移動済み)   :「有効なバインディングが見つからない」と見なし、現在のスコープで再宣言
    → x を見つけた(不変、生存中):E2010 再代入不可
    → 見つからない              :現在のスコープで新規宣言(唯一の宣言パス)

mut x = value:
    → 現在のスコープに x が既に存在 :E2002 二重定義
    → 外側スコープに x が存在       :E2013 シャドウイング禁止(明示的な新宣言は外側と同名不可)
    → 競合なし              :新規可変宣言
  • 同一スコープ内:任何名前を宣言できるのは1回のみ(E2002)
  • 内側 sans mut:外側を優先検索、代入またはエラー
  • 内側 avec mut:明示的な新宣言、外側と同名は不可(E2013)

同一スコープ内

yaoxiang
x = 10
x = 20              // E2002:'x' はこのスコープで既に定義済み

mut y = 10
y = 20              // OK:同一バインディング、再代入
mut y = 30          // E2002:'y' はこのスコープで既に定義済み

z = 10
mut z = 20          // E2002:'z' はこのスコープで既に定義済み(mut は既存宣言をオーバーライド不可)

移動後の再バインディング

所有権を持つ不変変数は、その値が move(消費)された場合、旧バインディングは moved 状態になります——名前はまだスコープスロットを占めていますが、値にはアクセスできません。このとき x = value は旧バインディングを変更するのではなく、同一スコープ内で x を再宣言します。

代入優先検索の「移動済み」分岐:
  x は現在のスコープに存在するが、移動済み状態
    → コンパイラは「有効なバインディングが見つからない」と見なす
    → 現在のスコープで x を再宣言(古い移動済みスロットをオーバーライド)

コアメカニズム: 旧値が消費された後、バインディングは無効になり、名前は未初期化状態に戻ります。これはシャドウイングではありません——旧バインディングは既に存在しません。

yaoxiang
// パイプライン型データフロー:各ステップが旧値を消費し、新値を生成
data = fetch()           // 不変、所有権を保持
data = transform(data)   // data を move → 旧 data は無効、新 data が再バインディング
data = filter(data)      // 同上
process(data)

// 等価な明示的写法(比較):
data1 = fetch()
data2 = transform(data1)  // data1 が moveされ、使用不可
data3 = filter(data2)     // data2 が moveされ、使用不可
process(data3)

意味的分離:

操作意味メカニズム構文
再バインディング旧値消失、新値誕生move + 再宣言x = f(x)
インプレース変更同一メモリ位置の値変化mut 代入mut x; x = v

なぜこれはシャドウイングと異なるか:

  • シャドウイング(Rust の let x = ...):旧バインディングはまだ存在し、新バインディングに隠されているだけ
  • Move 後再バインディング:旧バインディングは既に消費済み、名前は未初期化状態に戻り、再宣言が唯一の道

制約:

  • 所有権を持つ値のみが move 可能。参照(&T&mut T)はコピー而非移動
  • Move チェックはコンパイル時に完了し、移动済み状態の変数を任意の式で読み取ると E2014 を送出
  • IDE は移動済み変数に灰色ヒントを表示し、その名前が未初期化状態であることを示せる
yaoxiang
// move 後の読み取り → エラー
data = fetch()
result = process(data)   // data が move
print(data)              // E2014:'data' は既に move され、使用不可

// 参照は move をトリガーしない
ref_data = &value
copy1 = ref_data         // 参照をコピー、ref_data は使用可能
copy2 = ref_data         // OK

// スコープ間:moved 状態が貫通
data = fetch()
{
    data = transform(data)  // 外側 data を move → 再バインディング(内側新規宣言)
    print(data)             // OK:内側 data を使用
}
print(data)                 // E2014:外側 data は既に move 済み

スコープ間

yaoxiang
// 外側不変、内側代入 → 不変変数は再代入不可
x = 10
{
    x = 20          // E2010:'x' は不変で、再代入不可
}
{
    mut x = 20      // E2013:既存変数 'x' をシャドウイング不可(明示的新バインディング)
}

// 外側 mut、内側代入 → 同一バインディングを変更
mut y = 10
{
    y = 20          // OK:同一バインディング、&mut トークン経由で変更
}
print(y)            // 20

// 外側 mut、内側で同名宣言不可
mut z = 10
{
    z = 30          // OK:同一バインディング
}
{
    mut z = 30      // E2013:既存変数 'z' をシャドウイング不可
}

// 多層ネスト:mut が全レベルを貫通
mut a = 0
{
    {
        a = 10      // OK
    }
}
print(a)            // 10

// 不変は全レベルを貫通しても再代入不可
b = 0
{
    {
        b = 10      // E2010:'b' は不変で、再代入不可
    }
}

for ループ

yaoxiang
// ループ変数は各反復で新バインディング、修正ではない
for i in 1..5 {
    print(i)        // OK:各反復で新値をバインディング
    i = 10          // E2010:不変ループ変数は再代入不可
}

for mut i in 1..5 {
    i = 10          // OK:可変ループ変数
}

// ループ変数は外側をシャドウイング不可
i = 0
for i in 1..5 {     // E2013:既存変数 'i' をシャドウイング不可
}

// mut 外側アキュムレータはループボディ内で変更可能
mut sum = 0
for i in 1..5 {
    sum = sum + i   // OK:同一バインディング、&mut トークン経由で変更
}
print(sum)          // 15

// 不変外側はループボディ内で変更不可
sum2 = 0
for i in 1..5 {
    sum2 = sum2 + i // E2010:'sum2' は不変で、再代入不可
}

関連エラーコード

エラーコードメッセージトリガーシナリオ
E2002'{name}' is already defined in this scope同一スコープでの重複宣言(mut 与否問わない)
E2010Cannot assign to immutable variable '{name}'内側 sans mut 代入時、外側変数が不変で未 moved
E2013Cannot shadow existing variable '{name}'内側の明示的宣言(mut x または x: Type)が外側と同名
E2014'{name}' has been moved and cannot be used移動済み変数の読み取り

第5章:モジュール構成

5.1 ディレクトリ構造

src/
├── main.yx          // メインモジュール
├── math/
│   ├── index.yx     // 数学モジュールエントリ
│   ├── vector.yx    // ベクトルモジュール
│   └── matrix.yx    // 行列モジュール
└── utils/
    ├── index.yx     // ユーティリティモジュールエントリ
    └── string.yx    // 文字列ユーティリティ

5.2 モジュールエントリ

ディレクトリ内の index.yx ファイルがモジュールエントリになります:

yaoxiang
// math/index.yx
use math.vector
use math.matrix

pub Vector = vector.Vector
pub Matrix = matrix.Matrix

5.3 相対インポート

yaoxiang
// math/vector.yx 内での記述
use math.matrix  // 絶対インポート
use .matrix      // 相対インポート(同一ディレクトリ)

付録:モジュール構文早見表

A.1 モジュール即ファイル

// ファイル名.yx がモジュール名
Import ::= 'use' ModuleRef

A.2 インポート・エクスポート

yaoxiang
// インポート
use std.io
use std.io.{print, read}
use std.io as io

// エクスポート
pub pi: Float = 3.14159
pub sqrt: (x: Float) -> Float = (x) => { ... }