(вопрос отредактирован и переписан с учетом результатов обсуждения в чате)
В одной строке: при заданном состоянии в монаде состояния вычислить монадическую функцию один раз, кэшировать результаты.
Я пытаюсь кэшировать результат оценки функции, где ключом кэша является состояние монады State, и где меня не волнуют возможные побочные эффекты: т. е. даже если тело функции может измениться в теории , я знаю, что он будет независим от государства:
f x = state { return DateTime.Now.AddMinutes(x) }
g x = state { return DateTime.Now.AddMinutes(x) }
Здесь g 10
и f 10
должны давать один и тот же результат, они не могут отличаться в результате двойного вызова DateTime.Now
, т. е. они должны быть детерминированными. В качестве аргумента, состояние переменной здесь x
.
Точно так же (g 10) - (f 5)
должно давать ровно 5 minutes
, а не на микросекунду больше или меньше.
Узнав, что кэширование не работает, я упростил более сложное решение до минимума, используя Шаблон запоминания Дона Сайма с картами (или dict).
Шаблон запоминания:
module Cache =
let cache f =
let _cache = ref Map.empty
fun x ->
match (!_cache).TryFind(x) with
| Some res -> res
| None ->
let res = f x
_cache := (!_cache).Add(x,res)
res
Кэширование предполагается использовать как часть построителя вычислений в методе Run:
type someBuilder() =
member __.Run f =
Log.time "Calling __.Run"
let memo_me =
fun state ->
let res =
match f with
| State expr - expr state
| Value v -> state, v
Log.time ("Cache miss, adding key: %A", s)
res
XCache.cache memo_me
Это не работает, потому что функция кеша каждый раз разная из-за закрытия, что приводит к промаху кеша каждый раз. Он должен быть независим от expr
выше и зависеть только от state
.
Я попытался разместить _cache
вне функции кеша на уровне модуля, но тогда возникает проблема обобщения:
Ограничение стоимости. Предполагается, что значение _cache имеет универсальный тип
Либо определите _cache как простой термин данных, сделайте его функцией с явными аргументами, либо, если вы не хотите, чтобы он был универсальным, добавьте аннотацию типа .
Который я затем попытался решить с помощью аннотаций типов, но в итоге я не смог использовать его в общей функции по той же причине: для этого требовались аннотации определенного типа:
let _cache<'T, 'U when 'T: comparison> ref : Map<'T, 'U> = ref Map.empty
Edit, рабочая версия всего построителя вычислений
Вот построитель вычислений, как было сказано в комментариях, протестировано в FSI. Кэширование должно зависеть исключительно от TState
, а не от 'TState -> 'TState * 'TResult
в целом.
type State<'TState, 'TResult> = State of ('TState -> 'TState * 'TResult)
type ResultState<'TState, 'TResult> =
| Expression of State<'TState, 'TResult>
| Value of 'TResult
type RS<'S, 'T> = ResultState<'S, 'T>
type RS =
static member run v s =
match v with
| Value item -> s, item
| Expression (State expr) -> expr s
static member bind k v =
match v with
| Expression (State expr) ->
Expression
<| State
(fun initialState ->
let updatedState, result = expr initialState
RS.run (k result) updatedState
)
| Value item -> k item
type MyBuilder() =
member __.Bind (e, f) = RS.bind f e
member __.Return v = RS.Value v
member __.ReturnFrom e = e
member __.Run f =
printfn "Running!"
// add/remove the first following line to see it with caching
XCache.cache <|
fun s ->
match f with
| RS.Expression (State state) ->
printfn "Call me once!"
state s
| RS.Value v -> s, v
module Builders =
let builder = new MyBuilder()
// constructing prints "Running!", this is as expected
let create() = builder {
let! v = RS.Expression <| (State <| fun i -> (fst i + 12.0, snd i + 3), "my value")
return "test " + v
}
// for seeing the effect, recreating the builder twice,
// it should be cached once
let result1() = create()(30.0, 39)
let result2() = create()(30.0, 39)
Результат запуска примера в FSI:
Выполняется!
Позвоните мне один раз!
val it : (float * int) * string = ((42.0, 42), "проверить мое значение")
Позвоните мне один раз!
val it : ( float * int) * string = ((42.0, 42), "проверить мое значение")
obj
, но здесь есть'a * 'b
, или что-то подобное, говорящее, что(int * int)
делает не поддерживаетIComparable
). Я прислушиваюсь к вашему совету, но я не могу найти способ заставить его работать, кажется, что дженерики убиваются, как только я пытаюсь использовать оператор let на уровне модуля для захвата запоминаемых функций. Это похоже на это, но не то же самое: stackoverflow.com/questions/11845285/ - person Abel   schedule 23.11.2015Run
вместо того, чтобы делать только один вызов, а затем повторно вызывать результат. Но давайте посмотрим на примере. - person Fyodor Soikin   schedule 23.11.2015run
для привязки иrun
для стартера CU, так как я хочу кэшировать только один CU целиком, а не промежуточные результаты, которые мало что добавят. - person Abel   schedule 23.11.2015ref
? - person Kasey Speakman   schedule 23.11.2015Cache
. Я что-то упускаю? - person Fyodor Soikin   schedule 23.11.2015f
появляется только для каждого экземпляра. Вы не можете запомнить функциюf
на уровне выше, чем вы ее освоили. Имеет смысл? - person Fyodor Soikin   schedule 24.11.2015f
зависит только отstate
, одно и то же состояние независимо от закрытия должно давать одинаковый результат. Моя идея состояла в том (пока бесполезно), чтобы кеш зависел только отstate
, а не от экземпляраf
для каждого экземпляра, но использование этого шаблона создает кеш для каждого экземпляра, даже если я сделаю сам_cache
глобальным. - person Abel   schedule 24.11.2015state
— это функция, не так ли? Вы хотите сказать, что ожидаете иметь только ограниченное количество этих функций и не позволите потребителю создавать новые? - person Fyodor Soikin   schedule 24.11.2015state
создается при каждом вызовеbind
. Итак, если это так, как вы ожидаете увидеть одно и то жеstate
дважды? - person Fyodor Soikin   schedule 24.11.2015