Давайте использовать библиотеку заметок Люка Палмера: Data.MemoCombinators
import qualified Data.MemoCombinators as Memo
import Data.Function (fix) -- we'll need this too
Я собираюсь определить вещи, немного отличающиеся от того, как его библиотека, но в основном это то же самое (и, кроме того, совместимо). «Запоминающаяся» вещь берет себя в качестве входных данных и производит «настоящую» вещь.
type Memoizable a = a -> a
«Памятка» берет функцию и создает ее памятную версию.
type Memoizer a b = (a -> b) -> a -> b
Давайте напишем небольшую функцию, чтобы соединить эти две вещи. Учитывая Memoizable
функцию и Memoizer
, нам нужна результирующая запомненная функция.
runMemo :: Memoizer a b -> Memoizable (a -> b) -> a -> b
runMemo memo f = fix (f . memo)
Это немного волшебства с использованием комбинатора точек фиксирования (fix
). Не бери в голову это; Вы можете погуглить, если вы заинтересованы.
Итак, давайте напишем Memoizable
версию классического примера fib:
fib :: Memoizable (Integer -> Integer)
fib self = go
where go 0 = 1
go 1 = 1
go n = self (n-1) + self (n-2)
Использование соглашения self
делает код простым. Помните, что self
- это то, что мы ожидаем, чтобы стать запомненной версией этой самой функции, поэтому рекурсивные вызовы должны быть на self
. Теперь запустите GHCI.
ghci> let fib' = runMemo Memo.integral fib
ghci> fib' 10000
WALL OF NUMBERS CRANKED OUT RIDICULOUSLY FAST
Крутая вещь в runMemo
заключается в том, что вы можете создать более одной только что запомненной версии одной и той же функции, и они будут не совместно использовать банки памяти. Это означает, что я могу написать функцию, которая локально создает и использует fib'
, но затем, как только fib'
выходит из области видимости (или раньше, в зависимости от интеллекта компилятора), может собирать мусор . Не нужно запоминать на верхнем уровне . Это может или не может хорошо играть с техниками запоминания, которые основаны на unsafePerformIO
. Data.MemoCombinators
использует чистый ленивый Trie, который идеально подходит для runMemo
. Вместо того, чтобы создавать объект, который по сути становится менеджером заметок, вы можете просто создавать заметки по запросу. Уловка в том, что если ваша функция рекурсивная, она должна быть записана как Memoizable
. Хорошей новостью является то, что вы можете подключить любой Memoizer
, который пожелаете. Вы можете даже использовать:
noMemo :: Memoizer a b
noMemo f = f
ghci> let fib' = runMemo noMemo fib
ghci> fib' 30 -- wait a while; it's computing stupidly
1346269