非専門的シンギュラリティー研究所

無限に動き続けるシステムを表す方法を AI なども使って考えていきます。

関数プログラミングの帰納的関数(15)

再帰的呼び出しを含む関数を表す代数的構造について ChatGPT で調べてみました。

fibonacciGenerator を ref や mutable を使わないように書き換えることはできますか

と入力すると以下の結果となりました。

はい、`ref` や `mutable` を使わずに `fibonacciGenerator` を書き換えることができます。
クロージャ を使い、状態 `(a, b)` を 関数引数 として持ち回ることで実現できます。

1. `ref` や `mutable` を使わない `fibonacciGenerator`

let fibonacciGenerator () =
    let rec next (a, b) =
        fun () -> (a, next (b, a + b)) // 現在の値 `a` を返し、新しい関数を生成
    next (0, 1)

// 使い方
let nextFib1, nextGen1 = fibonacciGenerator () ()  // 0 を取得
let nextFib2, nextGen2 = nextGen1 ()  // 1 を取得
let nextFib3, nextGen3 = nextGen2 ()  // 1 を取得
let nextFib4, nextGen4 = nextGen3 ()  // 2 を取得
let nextFib5, nextGen5 = nextGen4 ()  // 3 を取得

printfn "%d" nextFib1  // 0
printfn "%d" nextFib2  // 1
printfn "%d" nextFib3  // 1
printfn "%d" nextFib4  // 2
printfn "%d" nextFib5  // 3

2. 仕組み

クロージャによる状態の持ち回り

  • `next (a, b)` は `(a, b)` を 状態 として持つ。
  • `fun () -> (a, next (b, a + b))` という 無名関数を返す
    • `a` は現在のフィボナッチ数。
    • `next (b, a + b)` を呼び出して、新しいフィボナッチ関数を作る。

実行の流れ

1. `fibonacciGenerator ()` を呼ぶと、最初の `(0, 1)` を持つ無名関数が返る。
2. `nextFib1, nextGen1 = fibonacciGenerator () ()` で `0` を取得し、次のジェネレーター `nextGen1` を取得。
3. `nextGen1 ()` を呼ぶと `(1, nextGen2)` が返り、次の値 `1` を取得。
4. 以降、`nextGenX ()` を呼ぶたびに次のフィボナッチ数が得られる。

3. `ref` を使わないメリット

方法 特徴
`ref` を使う 状態を変数として持つので、値の更新が簡単。ただし、関数型の純粋性を失う
`クロージャ` を使う 状態を関数の引数として持ち回るため、関数型プログラミングらしいスタイルになる。

`ref` を使わないことで、関数型プログラミング純粋な形フィボナッチ数列を表現できました!

この関数を「不動点コンビネータ」で表すことはできますか

と入力すると以下の結果となりました。

1. 不動点コンビネータ(Y コンビネータ)とは?

不動点コンビネータ(Fixed-Point Combinator)とは、
自己再帰する関数を定義するための高階関数 です。
特に、λ計算の文脈では Y コンビネータ として知られています。

Y コンビネータは、次のように定義されます。

\displaystyle
Y(f) = f (Y(f))

つまり、`Y(f)` は `f` の不動点 `F` を見つけます。

\displaystyle
F = f(F)

これを F# で書くと:

let y f = (fun x -> f (fun v -> x x v)) (fun x -> f (fun v -> x x v))

これは 自己参照的な関数を引数に取り、その不動点を求める関数 です。

2. `fibonacciGenerator` を Y コンビネータで書き換える

`fibonacciGenerator` は次のように書かれています。

let fibonacciGenerator () =
    let rec next (a, b) =
        fun () -> (a, next (b, a + b))
    next (0, 1)

この `next` 関数 を Y コンビネータを使って表現します。

3. Y コンビネータを使った Fibonacci 関数

Y コンビネータを利用して、フィボナッチ数列の無限ジェネレーターを定義すると:

let y f = (fun x -> f (fun v -> x x v)) (fun x -> f (fun v -> x x v))

let fibonacciGenerator =
    y (fun next ->
        fun (a, b) ->
            fun () -> (a, next (b, a + b))
    ) (0, 1)

このコードのポイント:
1. `y` は不動点コンビネータ(Y コンビネータ

  • `y (fun next -> ...)` により、自己再帰的な `next` を作成

2. `next` の定義

  • `(a, b)` を受け取り、次の Fibonacci 数を計算する関数 を返す。

3. 最初の `(0, 1)` を適用

  • `y` に `(0, 1)` を適用することで、最初のジェネレーターを得る。

4. 実行例

let nextFib1, nextGen1 = fibonacciGenerator ()  // 0 を取得
let nextFib2, nextGen2 = nextGen1 ()  // 1 を取得
let nextFib3, nextGen3 = nextGen2 ()  // 1 を取得
let nextFib4, nextGen4 = nextGen3 ()  // 2 を取得
let nextFib5, nextGen5 = nextGen4 ()  // 3 を取得

printfn "%d" nextFib1  // 0
printfn "%d" nextFib2  // 1
printfn "%d" nextFib3  // 1
printfn "%d" nextFib4  // 2
printfn "%d" nextFib5  // 3

5. まとめ

  • Y コンビネータを適用することで `next` を明示的な `let rec` なしで再帰関数として定義できる。
  • 不動点」を用いた Fibonacci ジェネレーターの実装が可能。
  • F# の関数型の特性を生かし、クロージャを利用して状態を持たせられる。

このように、Y コンビネータを使って `fibonacciGenerator` を再帰的に定義することが可能 です!

F# の式の作る代数的構造を考えます。next を変数とみなします。この代数的構造に変数を付け加えた(多項式のような)代数的構造を考えます。この関数をこの(多項式のような)代数的構造で表してください

と入力すると以下の結果となりました。

F# の `fibonacciGenerator` に登場する `next` を 変数 とみなし、それを含む代数的構造を考えます。さらに、多項式のような代数的構造 でこの関数を表現します。

1. `fibonacciGenerator` の数式的な表現

関数 `next` は次のように定義されています。

\displaystyle
\text{next} (a, b) = \lambda () . (a, \text{next} (b, a + b))

この `next` は 再帰的な構造を持つ自己関数 です。

この関数を代数的な式として考えると、
「次の値を生成する演算」 を表す `N(a, b)` という演算子を導入できます。

\displaystyle
N(a, b) = a + X N(b, a + b)

ここで:

  • `N(a, b)` は「現在の値 `a` を返し、次の関数 `N(b, a + b)` を得る演算」
  • `X` は「遅延適用」を表す記号(無名関数 `fun () -> ...` に対応)

2. `fibonacciGenerator` を代数的構造で表現

この関数を 代数的構造多項式のような形) で記述すると:

\displaystyle
F = N(0, 1)

さらに、`N(a, b)` の定義を展開すると:

\displaystyle
F = 0 + X N(1, 1)

\displaystyle
N(1, 1) = 1 + X N(1, 2)

\displaystyle
N(1, 2) = 1 + X N(2, 3)

\displaystyle
N(2, 3) = 2 + X N(3, 5)

\displaystyle
N(3, 5) = 3 + X N(5, 8)

したがって、`F` を展開すると:

\displaystyle
F = 0 + X (1 + X (1 + X (2 + X (3 + X (5 + X (8 + \dots))))))

これは無限級数の形になっており、
多項式の無限展開のような形フィボナッチ数列を表現できます。

3. まとめ

  • `fibonacciGenerator` の構造を代数的に表すと:

\displaystyle
  N(a, b) = a + X N(b, a + b)
という 再帰的な代数的関係式 になる。

  • `X` を「遅延適用」を表す記号として導入すると、

\displaystyle
  F = 0 + X (1 + X (1 + X (2 + X (3 + X (5 + X \dots)))))
のような無限級数の形になる。

  • これは、多項式環に無限和を許した拡張のような構造 と解釈できる。

このように、F# の `fibonacciGenerator` は 多項式のような代数的構造 で表現できることが分かりました!