Подтвердить что ты не робот

Как вы делаете общую функцию memoize в Haskell?

Я видел другое сообщение об этом, но есть ли чистый способ сделать это в Haskell?

Как вторая часть, можно ли это сделать и без монадической функции?

4b9b3361

Ответ 1

Это в значительной степени следует http://www.haskell.org/haskellwiki/Memoization.

Вам нужна функция типа (a → b). Если он не называет себя, то вы можете просто написать простую оболочку, которая кэширует возвращаемые значения. лучший способ сохранить это отображение зависит от того, какие свойства вы можете эксплуатируют. Заказ в значительной степени является минимальным. С целыми числами вы можете построить бесконечный ленивый список или дерево, содержащее значения.

type Cacher a b = (a -> b) -> a -> b

positive_list_cacher :: Cacher Int b
positive_list_cacher f n = (map f [0..]) !! n

или

integer_list_cacher :: Cacher Int b
integer_list_cacher f n = (map f (interleave [0..] [-1, -2, ..]) !!
    index n where
        index n | n < 0  = 2*abs(n) - 1
        index n | n >= 0 = 2 * n

Итак, пусть это рекурсивный. Тогда вам нужно это, чтобы назвать не себя, но memoized версии, поэтому вы передаете это вместо:

f_with_memo :: (a -> b) -> a -> b
f_with_memo memoed base = base_answer
f_with_memo memoed arg  = calc (memoed (simpler arg))

Памятная версия - это, конечно же, то, что мы пытаемся определить.

Но мы можем начать с создания функции, которая кэширует свои входы:

Мы могли бы построить один уровень, передав функцию, которая создает структура, которая кэширует значения. За исключением того, что нам нужно создать версию f который уже имеет кэшированную функцию.

Благодаря лень, это не проблема:

memoize cacher f = cached where
         cached = cacher (f cached)

тогда нам нужно всего лишь использовать его:

exposed_f = memoize cacher_for_f f

В статье даются подсказки относительно того, как использовать класс типа, выбирающий на вход в функцию для выполнения вышеизложенного, а не выбор явного функция кеширования. Это может быть очень приятно, а не явно построение кеша для каждой комбинации типов ввода, мы можем косвенно объединить кеши для типов a и b в кеш для функции, принимающей a и b.

Одно последнее предостережение: использование этой ленивой техники означает, что кеш никогда не сжимается, он только растет. Если вы используете монаду IO, вы можете управлять этим, но это разумно зависит от шаблонов использования.

Ответ 2

Данные пакета-мемокомбинаторы при взломе предоставляют множество многократно используемых процедур memoization. Основная идея:

type Memo a = forall r. (a -> r) -> (a -> r)

т.е. он может memoize любую функцию из. Затем модуль предоставляет некоторые примитивы (например, unit :: Memo () и integral :: Memo Int) и комбинаторы для построения более сложных таблиц memo (например, pair :: Memo a -> Memo b -> Memo (a,b) и list :: Memo a -> Memo [a]).

Ответ 3

Вы можете изменить решение Джонатана с помощью небезопасногоPerformIO, чтобы создать "чистую" memoizing версию вашей функции.

import qualified Data.Map as Map
import Data.IORef
import System.IO.Unsafe

memoize :: Ord a => (a -> b) -> (a -> b)
memoize f = unsafePerformIO $ do 
    r <- newIORef Map.empty
    return $ \ x -> unsafePerformIO $ do 
        m <- readIORef r
        case Map.lookup x m of
            Just y  -> return y
            Nothing -> do 
                    let y = f x
                    writeIORef r (Map.insert x y m)
                    return y

Это будет работать с рекурсивными функциями:

fib :: Int -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib_memo (n-1) + fib_memo (n-2)

fib_memo :: Int -> Integer
fib_memo = memoize fib

Хотя этот пример является функцией с одним целым параметром, тип memoize говорит нам, что он может использоваться с любой функцией, которая принимает сопоставимый тип. Если у вас есть функция с несколькими параметрами, просто группируйте их в кортеж, прежде чем применять memoize. F.i:.

f :: String -> [Int] -> Float
f ...

f_memo = curry (memoize (uncurry f))

Ответ 4

Выполняя прямой перевод с более императивных языков, я придумал это.

memoize :: Ord a => (a -> IO b) -> IO (a -> IO b)
memoize f =
  do r <- newIORef Map.empty
     return $ \x -> do m <- readIORef r
                       case Map.lookup x m of
                            Just y  -> return y
                            Nothing -> do y <- f x
                                          writeIORef r (Map.insert x y m)
                                          return y

Но это как-то неудовлетворительно. Кроме того, Data.Map ограничивает параметр как экземпляр Ord.

Ответ 5

Если ваши аргументы будут натуральными числами, вы можете сделать это просто:

memo f = let values = map f [0..]
     in \n -> values !! n

Однако это не поможет вам с переполнением стека и не работает с рекурсивными вызовами. Вы можете увидеть некоторые более благоприятные решения в http://www.haskell.org/haskellwiki/Memoization.