Skip to content

モジュールシステム仕様 ​

本文書は YaoXiang プログラミング言語のモジュールシステム仕様を定義する。モジュール定義、インポート・エクスポート、スコープを含む。


第一章:モジュール定義 ​

1.1 モジュールの基礎 ​

モジュールはファイルを境界として使用する。各 .yx ファイルが一つのモジュールとなる。

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

1.2 モジュールの命名規則 ​

  • モジュール名はファイル名によって決定される
  • ファイル拡張子 .yx はモジュール名に含まれない
  • モジュール名は PascalCase で命名する

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

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.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.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 は宣言なのか代入なのか?一つの原則に従う:

代入優先。 宣言は一度だけ、代入は何度も行われる。高頻度の操作に最短経路を取らせる。

x = value:
  スコープチェーンを外側にたどって x を検索
    → mut x が見つかった    :代入、OK(&mut トークン経由)
    → x が見つかった(不変、生存中):E2010 再代入不可
    → 見つからない            :現在のスコープで新規宣言(唯一の宣言経路)

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

ブロックは真のスコープ:「スコープチェーンをたどる」の前提は、各 {} ブロックが実際に一層を確立することである——内側で新規宣言された名前は外側に漏れない(§2.15 参照)。

同一スコープ ​

yaoxiang
x = 10
x = 20              // E2010:'x' は不変、再代入不可

mut y = 10
y = 20              // OK:同一バインディング、再代入
mut y = 30          // E2002:'y' は既にこのスコープで定義済み(明示的な新規宣言が衝突)

z = 10
mut z = 20          // E2002:'z' は既にこのスコープで定義済み(mut は既存宣言を上書き不可)

x = 20 が報告するのは E2010(再代入不可)であり E2002(重複定義)ではない点に注意: mut を伴わない x = value の意味は代入(スコープチェーンを辿る検索)であり、「x を再宣言する」ではない。明示的な新規宣言となるのは mut x = value のみで、衝突時に E2002 が報告される。

Move 後の再バインディング ​

不変変数が所有権を保持している場合、その値が move(消費)されると、元バインディングは moved 状態になる——名前は依然としてスコープのスロットを占有するが、値はもはやアクセスできない。

moved は「宣言判定」の入力ではない。 その名前を再度取得する手段は明示的な再宣言である:

yaoxiang
// パイプライン式データフロー:各ステップで旧値を消費し新値を生成
mut data = fetch()           // 明示的な可変宣言
mut data = transform(data)   // E2002:同一スコープに既に data がある

「moved → 再宣言可能」を採用しない理由(2026-09-19 改訂):

move はパス依存のデータフロー属性である——if c { move p } の後、p は一つのパスでは move 済み、別のパスでは未 move という「move 済みの可能性」であり、布尔的な真偽ではない。バインディングレベルのフラグ構造では分岐合流を表現できず、それを「名前再宣言可能」のスイッチとして使うと誤った診断を与える(単一分岐の move が「move 済み」として扱われる)。

真の move 解析は layers/ownership.rs にある:関数本体の CFG を構築し、 Alive < Moved < Dropped の束でデータフローを行い、分岐合流では max を取る(保守的)、読み取りチェックポイントで E2014/E2018 を報告する。これは「ここで読めるか」に答えるものであり、「この名前を再宣言できるか」は §4.3 の代入優先規則が独立して答える。

二つの責務は異なり、結合すべきではない。これが旧文の「moved 済み分岐」が決して到達不能であった理由である:それは決して書き込まれないフラグに依存していた。

再バインディングは mut による明示宣言で実現する:

yaoxiang
// 等価な明示的書き方
mut data1 = fetch()
data2 = transform(data1)  // data1 は move され、再使用不可
mut data3 = filter(data2)

意味の分離:

操作意味機構構文
再バインディング旧値が消滅し、新値が誕生move + 新規宣言mut x = f(x)(新名または mut)
インプレース変更同一メモリ位置の値変化mut 代入mut x = v; x = w

制約:

  • 所有権を保持する値のみが move される。参照(&T、&mut T)はコピーされ move されない
  • Move チェックはコンパイル時に行われる(CFG + データフロー)、moved 状態の変数を如何なる式で読み取っても E2014 が報告される
  • IDE は moved 変数に灰色ヒントを表示し、その名前が未初期化状態であることを示せる
  • 宣言には初期値が必要:LetStmt ::= ('mut')? Identifier (':' TypeExpr)? '=' Expr (§3.2)——x: Int のような純粋な注釈宣言は文法違反となり、E0012 が報告される
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}'内側に mut がない代入時、外側変数が不変かつ未 moved
E2013Cannot shadow existing variable '{name}'内側の明示的宣言(mut x または x: Type)が外側と同名
E2014'{name}' has been moved and cannot be usedmoved 済み変数の読み取り

第五章:モジュール構成 ​

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) => { ... }