Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

References

💡 お知らせ: このドキュメントはAIによって翻訳されています。表現に違和感がある場合は、原文(英語)を参照するか、翻訳にご協力ください。

Flix はミュータブル(mutable)でスコープ付きの参照(reference)をサポートしています。参照とは、その値が時間とともに変化しうるボックスです。参照に対する 3 つの主要な操作は次のとおりです。

  • 新しい参照を作成する Ref.fresh(rc, e)
  • 参照をデリファレンス(dereference)する Ref.get(e)
  • 参照に代入する Ref.put(e, e)

Flix では、参照の型は Ref[t, r] であり、t は要素の型、r はそのリージョンです。Flix のすべてのミュータブルなメモリと同様に、すべての参照は何らかのリージョンに属していなければなりません。参照に対する読み取りと書き込みは、エフェクトを伴う(effectful)操作です。たとえば、参照 Ref[t, r] の値を読み取ると、エフェクト r を伴います。

Ref.fresh(rc, e) 操作はヒープのリージョン内に参照セルを割り当て、その位置を返します。Ref.get 操作はある位置をデリファレンスし、参照セルの内容を返します。そして代入操作 Ref.put は参照セルの値を変更します。直感的には、参照セルは、値を変更できる単一のフィールドを持つ「オブジェクト」と考えることができます。

参照の割り当て

参照セルは Ref.fresh(rc, e) 関数で割り当てます。たとえば次のようになります。

region rc {
    let c = Ref.fresh(rc, 42);
    println(Ref.get(c))
}

ここでは rc という名前のリージョンを導入しています。このリージョンの内部で、値 42 を持つ c という参照セルを作成し、それをデリファレンスして出力しています。

参照のデリファレンス

参照セルには Ref.get 関数でアクセスします(デリファレンスします)。たとえば次のようになります。

region rc {
    let c = Ref.fresh(rc, 42);
    let x = Ref.get(c);
    let y = Ref.get(c);
    println(x + y)
}

ここでは、このプログラムは 42 + 42 = 84 を出力します。

代入

参照セルの値を更新することができます。たとえば次のようになります。

region rc {
    let c = Ref.fresh(rc, 0);
    Ref.put(Ref.get(c) + 1, c);
    Ref.put(Ref.get(c) + 1, c);
    Ref.put(Ref.get(c) + 1, c);
    println(Ref.get(c))
}

ここでは、このプログラムは値 0 を持つ参照セル c を作成します。そしてそのセルをデリファレンスし、その値を 3 回インクリメントします。したがって、このプログラムは 3 を出力します。

例:シンプルなカウンター

参照を使って、シンプルなカウンターを実装することができます。

enum Counter[r: Region] { // ここでの Region は型カインド(type-kind)です
    case Counter(Ref[Int32, r])
}

def newCounter(rc: Region[r]): Counter[r] \ r = Counter.Counter(Ref.fresh(rc, 0))

def getCount(c: Counter[r]): Int32 \ r =
    let Counter.Counter(l) = c;
    Ref.get(l)

def increment(c: Counter[r]): Unit \ r =
    let Counter.Counter(l) = c;
    Ref.put(Ref.get(l) + 1, l)

def main(): Unit \ IO =
    region rc {
        let c = newCounter(rc);
        increment(c);
        increment(c);
        increment(c);
        getCount(c) |> println
    }

ここでは、Counter データ型がリージョンの型パラメータを持っています。これは、カウンターが内部でリージョンを必要とする参照を使用しているため必須です。したがって、Counter もスコープ付きとなります。newCounter 関数が新しい Counter を作成するためにリージョンハンドルを必要とすることに注意してください。さらに、getCount 関数と increment 関数がともに r エフェクトを持つことに注意してください。

エイリアシングと参照への参照

エイリアシングこそが参照の目的なので、参照は自然にこれをサポートします。たとえば次のようになります。

region rc {
    let l1 = Ref.fresh(rc, 42);
    let l2 = l1;
    Ref.put(84, l2);
    println(Ref.get(l1))
}

これは 84 を出力します。なぜなら、l1 が指している参照セルが、エイリアス l2 を通じて変更されるためです。

参照は、次の例が示すように、参照を指すこともできます。

region rc {
    let l1 = Ref.fresh(rc, 42);
    let l2 = Ref.fresh(rc, l1);
    let rs = Ref.get(Ref.get(l2));
    println(rs)
}

ここでは、l2 の型は Ref[Ref[Int32, rc], rc] です。

ミュータブルなタプルとレコード

Flix のタプルとレコードはイミュータブル(不変)です。しかし、タプルとレコードはミュータブルな参照を含むことができます。

たとえば、次のものは 2 つのミュータブルな参照を含むペアです。

region rc {
    let p = (Ref.fresh(rc, 1), Ref.fresh(rc, 2));
    Ref.put(123, fst(p))
};

このペアの型は (Ref[Int32, rc], Ref[Int32, rc]) です。この代入はペアそのものを変更するのではなく、第 1 要素にある参照セルの値を変更します。

同様に、次のものは 2 つのミュータブルな参照を含むレコードです。

region rc {
    let r = { fstName = Ref.fresh(rc, "Lucky"), lstName = Ref.fresh(rc, "Luke") };
    Ref.put("Unlucky", r#fstName)
};

このレコードの型は { fstName = Ref[String, rc], lstName = Ref[String, rc] } です。ここでも、この代入はレコードそのものを変更するのではなく、fstName ラベルに対応する参照セルの値を変更します。