data.memoconvinatorsはどのように機能しますか?
-
12-11-2019 - |
質問
data.memocombinators ですが、その中心がどこにあるのか本当にわかりません。
これらのコンビネータのすべての背後にあるものは、実際の世界のプログラミングでプログラムをスピードアップする方法ののすべての背後にあるものを説明してください。
この実装の詳細を探しています。私はどのようなメモイケーションが何であるかを理解していますではありません一般的に機能する方法についての説明を探しています。
解決
このライブラリは、メモリ化の公知の技術の直接的な組み合わせです。正規の例から始めましょう:
fib = (map fib' [0..] !!)
where
fib' 0 = 0
fib' 1 = 1
fib' n = fib (n-1) + fib (n-2)
.
あなたが言ったことをあなたがこれがどのように機能しているのかと理由を知っているということを意味するものを解釈します。だから私は組み合わせに焦点を当てます。
私たちはESSENTIALLに(map f [0..] !!)
のアイデアを把握し、一般化しようとしています。この関数のタイプは(Int -> r) -> (Int -> r)
です。これは意味があります.Int -> r
から関数がかかり、同じ関数のメモイマバージョンを返します。意味的に識別的なものであり、このタイプを持つ関数は、 "Int
のメモイザ"と呼ばれます(Meid
さえ、メモイクではありません)。この抽象化に一般化します。
type Memo a = forall r. (a -> r) -> (a -> r)
.
それで、Memo a
用のメモイザは、a
から何でも関数を取り、メモリングされた(またはそうでない)意味的に同じ関数を返します。
さまざまなメモイザーのアイデアは、データ構造を持つドメインを列挙する方法を見つけ、それらの上に関数をマッピングしてからデータ構造を索引付けします。 a
は良い例です:
bool :: Memo Bool
bool f = table (f True, f False)
where
table (t,f) True = t
table (t,f) False = f
.
bool
からの関数はペアと同等です。だから私たちはペアとバックにマッピングするだけです。重要な点は、ドメインを列挙することによって、引数(ここではBool
の最後の引数)のラムダの上の関数の評価を持ち上げることです。
Memoizing table
は、紹介されている以外は同様のストーリーです。そのため、Maybe a
用のメモイザは、a
用のメモイザを引数として取ります。
maybe :: Memo a -> Memo (Maybe a)
maybe ma f = table (f Nothing, ma (f . Just))
where
table (n,j) Nothing = n
table (n,j) (Just x) = j x
.
残りのライブラリはこのテーマの単なるバリエーションです。
整数型をメモリングする方法は、Just
よりも適切な構造を使用します。それは少し関わっていますが、基本的には無限のツリーを作成するだけです(構造を解明するためのバイナリの数字を表す):
1
10
100
1000
1001
101
1010
1011
11
110
1100
1101
111
1110
1111
.
ツリー内の数字を見上げると、その表現のビット数に比例して実行時間があります。
SCLVを指すように、ConalのMemotrie Libraryは同じ基本的なテクニックを使用していますが、コンビネータプレゼンテーションの代わりに型抜きプレゼンテーションを使用します。私たちは同時にライブラリを同時にリリースしました(確かに数時間以内です)。 CONALの簡単な場合では使いやすい(関数、Maybe
は1つだけです。
boundedMemo :: Integer -> Memo Integer
boundedMemo bound f = \z -> if z < bound then memof z else f z
where
memof = integral f
.
Project Eulerの問題の1つを実装するために必要な値の値のみをメモリ化する。
モナド上のオープンフィックスポイント機能を公開するための他のアプローチがあります。
memo :: MonadState ... m => ((Integer -> m r) -> (Integer -> m r)) -> m (Integer -> m r)
.
それはさらに柔軟性を可能にします。キャッシュ、LRUなどをパージすることは、使用するためのお尻の痛みであり、また思い出される機能に厳密さの制約を与えます(例えば、無限の左再帰はありません)。このテクニックを実装するライブラリがあるとは思わない。
それはあなたが興味があるものに答えましたか?そうでなければ、おそらくあなたが混乱しているポイントを明確にする?
他のヒント
心臓はbits
関数です:
-- | Memoize an ordered type with a bits instance.
bits :: (Ord a, Bits a) => Memo a
bits f = IntTrie.apply (fmap f IntTrie.identity)
.
unit :: Memo ()
値を与えることができる唯一の関数(Trivial Memo a
を除く)です。これは、このページ
作品の中には、inttrieによって行われています:> http://hackage.haskell .ORG /パッケージ/データ-TTRIE-0.0.4
ルークのライブラリは、ConalのMemotrie Libraryのバリエーションです。
機能メモリングの背後にある一般的な概念は、a -> b
から関数を取り、a
のすべての可能な値とb
の値を含むデータストラクチャを中心にマッピングすることです。そのようなデータストラクチャは2つの方法で怠惰になるべきです - 最初にそれが保持する値で怠惰になるべきです。第二に、それはそれ自体が怠惰に生産されるべきです。前者はデフォルトではない言語であります。後者は一般化試行を使用することによって達成されます。
メモンビネット、メモトリエなどのさまざまなアプローチはすべて、ますます複雑な構造のための試みの簡単な構造を可能にするために、個々の種類のデータストラクチャに対抗する部分の組成を作成する方法だけです。
@luqui私には明確でないことを1つ:これは次のように同じ運用行動を持っていますか:
fib :: [Int]
fib = map fib' [0..]
where fib' 0 = 0
fib' 1 = 1
fib' n = fib!!(n-1) + fib!!(n-2)
.
上記は上位レベルでFIBをメモリングする必要があり、したがって2つの関数を定義する場合:
f n = fib!!n + fib!!(n+1)
.
その後 F 5 を計算した場合、 FIB 6 を計算する際に FIB 5 <>は再計算されない。メモイズコンビネータが同じ動作(すなわち、FIB計算の中の再計算を禁止するのではなくトップレベルのメモイ化)であるかどうかは明らかではなく、そうであれば、正確には正確に?