🐕

Functional Programming in C# まとめ 12章

2024/03/14に公開

11章のまとめは、こちら

12. Stateful programs and stateful computations

この章では以下の内容を説明する。

  • 何がプログラムをステートフルにするのか?
  • 状態を変更することなくステートフルなプログラムを書く
  • ランダムな構造を生成する
  • ステートフルな計算を合成する

12.1. Programs that manage state

using Rates = ImmutableDictionary<string, decimal>;

static (decimal, Rates) GetRate
    (Func<string, decimal> getRate, string ccyPair, Rates cache)
{
    if(cache.ContainsKey(ccyPair))
        return (cache[ccyPair], cache);
    
    var rate = getRate(ccyPair);
    return (rate, cache.Add(ccyPair, rate));
}

ステートフル計算は、状態を取り、新しい状態を返す関数。状態遷移とも呼ぶ。

このようにすることで、グローバル変数やその状態変更をせずに、ステートフルな(状態に依存した)計算を行うことができる。

12.2. A language for generationg random data

乱数生成器(Random)は、本当の乱数を生成しているわけではなく、乱数に見える値を生成しているだけである。そのため、シードを固定すると、常に同じ結果が得られるようになる。Nextを呼ぶ度に出力される値が変わるのは、乱数生成器の内部状態が変化しているからである。つまり、Nextには副作用がある。

ここで、副作用がない乱数生成器をつくりたい。

public delegate(T Value, int Seed) Generator<T>(int seed);

ここで、intの乱数生成器を作ってみる。※実装は一例

public static Generator<int> NextInt = (seed) => 
{
    seed ^= seed >> 13;
    seed ^= seed >> 18;
    int result = seed & 0x7fffffff;
    return (result, result);
};

これを基にして、boolの乱数生成器を考える。

public static Generator<bool> NextBool = (seed) =>
{
    var (i, newSeed) = NextInt(seed);
    return (i % 2 == 0, newSeed);
};

これは、NextIntに関数を適用しただけなので、をMapを使って一般化する。

public static Generator<R> Map<T, R>
    (this Generator<T> gen, Func<T, R> f)
    => seed =>
        {
            var (t, newSeed) = gen(seed);
            return (f(t), newSeed);
        };

この一般化を利用するとboolの乱数生成器は以下のように書き直せる

public static Generator<bool> NextBool =>
    from i in NextInt
    select i % 2 == 0;

毎回、関数のコンテナではよくわからなくなってしまうのですが、

  • i:NextIntにseedを与えたときに得られる結果の乱数
  • select:Map関数なので、結果は、seedを与えると乱数と新しいseedを出力する関数になる

ということで、新しい乱数生成器が得られます。

次に、intのペアを生成する場合を考える。

public static Generator<(int, int)> PairOfInts = (seed0) =>
{
    var (a, seedl) = NextInt(seed0);
    var (b, seed2) = NextInt(seed1);
    return ((a, b), seed2);
}

状態(seed)の扱いが煩雑。以下のように書きたい。

public static Generator<(int, int)> PairOfInts =>
{
    from a in NextInt
    from b in NextInt
    select (a, b);
}

BindとSelectManyを定義することで実現する。

public static Generator<R> Bind<T, R>
    (this Generator<T> gen, Func<T, Generator<R>> f)
    => seed0 =>
        {
            var (t, seed1) = gen(seed0);
            return f(t)(seed1)
        }

12.3. A general pattern for stateful computations

以下は、より一般的なステートフル計算のデリゲートである:

delegate(T Value, S State) StatefulComputation<S, T>(S state);
S -> (T, S)

※FPの用語では、状態monadと呼ばれるもの。

Discussion