私はfix
のドキュメントに少し混乱していました(ただし、今何をすべきかは理解していると思います)ので、ソースコードを調べました。それは私をより混乱させました:
fix :: (a -> a) -> a
fix f = let x = f x in x
これはどの程度正確に固定小数点を返しますか?
コマンドラインで試してみることにしました。
Prelude Data.Function> fix id
...
そして、そこにぶら下がっています。公平を期すために、これは少し遅い私の古いMacbookにあります。ただし、この関数をtooにすることはできません。idに渡されるものは同じものを返すためです(CPU時間を消費しないことは言うまでもありません)。何が悪いのですか?
あなたは何も悪いことをしていません。 _fix id
_は無限ループです。
fix
が関数の最小固定小数点を返すと言うとき、それは ドメイン理論 の意味での意味です。したがって、fix (\x -> 2*x-1)
はnotが_1
_を返すことになります。これは、_1
_はその関数の固定小数点ですが、 least1つはドメインの順序で。
ドメインの順序を1段落または2段落で説明することはできないので、上記のドメイン理論のリンクを参照します。これは優れたチュートリアルであり、読みやすく、非常にわかりやすくなっています。私はそれを強くお勧めします。
10,000フィートからのビューの場合、fix
はrecursionの概念をエンコードする高次関数です。式がある場合:
_let x = 1:x in x
_
結果は無限リスト_[1,1..]
_になりますが、fix
を使用して同じことを言うことができます。
_fix (\x -> 1:x)
_
(または単にfix (1:)
)は、_(1:)
_関数の固定小数点を見つけてくれます。IOW値x
このような_x = 1:x
_...のように上記で定義。定義からわかるように、fix
はこのアイデアにすぎません。再帰は関数にカプセル化されます。
これは、再帰の本当に一般的な概念でもあります-この方法で任意の再帰関数を記述できます 多相再帰を使用する関数を含む 。たとえば、典型的なフィボナッチ関数は次のとおりです。
_fib n = if n < 2 then n else fib (n-1) + fib (n-2)
_
このようにfix
を使用して書くことができます:
_fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))
_
演習:fix
の定義を展開して、fib
のこれら2つの定義が同等であることを示します。
しかし、完全な理解のために、ドメイン理論について読んでください。それは本当にクールなものです。
私はこれを完全に理解しているとは主張していませんが、これが誰かを助ける場合は...
fix
の定義を検討してください。 _fix f = let x = f x in x
_。気が遠くなる部分は、x
が_f x
_として定義されていることです。しかし、少し考えてみてください。
_x = f x
_
X = f xなので、その右側のx
の値を代入できますよね?だから….
_x = f . f $ x -- or x = f (f x)
x = f . f . f $ x -- or x = f (f (f x))
x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.
_
したがって、トリックは、終了するためにf
が何らかの構造を生成する必要があるため、後のf
が実際に完全な状態を気にせずに、その構造にパターンマッチングして再帰を終了できるようにする必要があります。パラメータの「値」(?)
もちろん、luquiが示すように、無限リストを作成するようなことをしたくない場合を除きます。
TomMDの階乗の説明は良いです。 Fixの型シグネチャは_(a -> a) -> a
_です。 _(\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)
_の型シグネチャは_(b -> b) -> b -> b
_、つまり_(b -> b) -> (b -> b)
_です。つまり、a = (b -> b)
と言えます。このようにして、fixは_a -> a
_、または実際には_(b -> b) -> (b -> b)
_という関数を受け取り、a
型の結果、つまり_b -> b
_を返します。つまり、別の機能です!
ちょっと待って、関数ではなく固定小数点を返すことになっていると思いました。まあ、そうですね(関数はデータなので)。あなたが階乗を見つけるための決定的な機能を私たちに与えたと想像することができます。再帰の方法がわからない関数を与えたため(そのパラメーターの1つが再帰に使用される関数です)、fix
に再帰の方法を教えました。
後のf
がパターンマッチングして終了できるように、f
は何らかの構造を生成する必要があると私が言ったことを覚えていますか?まあそれは正確ではないでしょうね。 TomMDは、x
を展開して関数を適用し、基本ケースに移行する方法を示しました。彼の機能では、彼はif/thenを使用し、それが終了の原因です。置換を繰り返した後、in
の定義全体のfix
部分は、最終的にx
に関して定義されなくなり、それが計算可能で完全な状態になります。
フィックスポイントを終了する方法が必要です。あなたの例を拡大すると、それが終了しないのは明らかです:
fix id
--> let x = id x in x
--> id x
--> id (id x)
--> id (id (id x))
--> ...
これは私が修正を使用する実際の例です(私は修正を頻繁に使用せず、おそらく疲れていた/これを書いたときに読み取り可能なコードについて心配していなかったことに注意してください):
(fix (\f h -> if (pred h) then f (mutate h) else h)) q
WTF、あなたは言う!ええ、はい、しかし、ここにはいくつかの本当に役立つ点があります。まず最初に、最初のfix
引数は通常、「再帰」ケースである関数である必要があり、2番目の引数は処理対象のデータです。名前付き関数と同じコードを次に示します。
getQ h
| pred h = getQ (mutate h)
| otherwise = h
あなたがまだ混乱しているなら、おそらく階乗はもっと簡単な例でしょう:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120
評価に注意してください:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 3 -->
let x = (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x in x 3 -->
let x = ... in (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 3 -->
let x = ... in (\d -> if d > 0 then d * (x (d-1)) else 1) 3
ああ、見ただけですか?そのx
は、then
ブランチ内の関数になりました。
let x = ... in if 3 > 0 then 3 * (x (3 - 1)) else 1) -->
let x = ... in 3 * x 2 -->
let x = ... in 3 * (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 2 -->
上記では、x = f x
を覚えておく必要があるため、x 2
だけではなく、末尾に2
の2つの引数があります。
let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->
そして、ここでやめます!
1つの明確な定義は 修正を再考することもできます!
私がそれを理解する方法は、それはあなたがそれを与える同じものを出力するように、それは関数の値を見つけます。問題は、常に未定義(または無限ループ、haskellでは、未定義ループと無限ループは同じ)または最も未定義のものが何でも選択されることです。たとえば、idでは、
λ <*Main Data.Function>: id undefined
*** Exception: Prelude.undefined
ご覧のとおり、undefinedは固定小数点であるため、fix
がそれを選択します。代わりに(\ x-> 1:x)を実行した場合。
λ <*Main Data.Function>: undefined
*** Exception: Prelude.undefined
λ <*Main Data.Function>: (\x->1:x) undefined
[1*** Exception: Prelude.undefined
したがって、fix
は未定義を選択できません。無限ループにもう少し接続するため。
λ <*Main Data.Function>: let y=y in y
^CInterrupted.
λ <*Main Data.Function>: (\x->1:x) (let y=y in y)
[1^CInterrupted.
繰り返しになりますが、若干の違いがあります。では、固定点は何ですか? repeat 1
。
λ <*Main Data.Function>: repeat 1
[1,1,1,1,1,1, and so on
λ <*Main Data.Function>: (\x->1:x) $ repeat 1
[1,1,1,1,1,1, and so on
同じです!これが唯一の固定小数点であるため、fix
はそれで解決する必要があります。申し訳ありませんfix
、無限ループがないか、未定義です。