Мне кажется, что вы пытаетесь определить монаду State и реализовать операцию Set как пользовательскую операцию.
Признаюсь, я никогда полностью не разбирался в пользовательских операциях в F# (и я много использовал F#). ИМХО, похоже, что пользовательские операции имели одну цель; включить синтаксис, подобный LINQ, в F#. Со временем кажется, что лишь немногие разработчики C# используют синтаксис, подобный LINQ (т.е. from x where y select z
), и лишь немногие разработчики F# используют вычислительное выражение query
. У меня здесь нет данных, но я просто исходил из примера кода, который я вижу.
Это может объяснить, почему документация по пользовательским операциям часто бывает краткой и трудной для понимания. Что это вообще значит? MaintainsVariableSpaceUsingBind: Indicates if the custom operation maintains the variable space of the query or computation expression through the use of a bind operation.
В любом случае, чтобы узнать немного больше о пользовательских операциях, я попытался реализовать монаду состояния с пользовательской операцией для set
, и я продвинулся немного дальше, но столкнулся с проблемой, которая, я думаю, является преднамеренным ограничением компилятора. Все еще думал, что делюсь им с надеждой, что это поможет OP продвинуться немного дальше.
Я выбрал это определение для State<_>
:
type [<Struct>] State<'T> = S of (Map<string, obj> -> 'T*Map<string, obj>)
State<_>
— это функция, которая при заданном глобальном состоянии (карте) создает значение (которое может быть получено из глобального состояния, но не обязательно) и потенциально обновляемое глобальное состояние.
return
или value
, как я обычно его называю, поскольку return
— это ключевое слово F#, которое легко определить, поскольку мы просто возвращаем v и необновленное глобальное состояние:
let value v = S <| fun m -> v, m
bind
полезен для связывания нескольких вычислений состояния вместе. Сначала запустите t
для глобального состояния и из возвращенного значения создайте второе вычисление и запустите через него обновленное глобальное состояние:
let bind uf (S t) = S <| fun m ->
let tv, tm = t m
let (S u) = uf tv
u tm
get
и set
используются для взаимодействия с глобальным состоянием:
let get k : State<'T option> = S <| fun m ->
match m |> Map.tryFind k with
| Some (:? 'T as v) -> Some v, m
| _ -> None, m
let set k v = S <| fun m ->
let m = m |> Map.add k (box v)
(), m
Я также создал некоторые другие методы, но в итоге конструктор был создан следующим образом:
type Builder() =
class
member x.Bind (t, uf) = bind uf t
member x.Combine (t, u) = combine u t
member x.Delay tf = delay tf
member x.For (s, tf) = forEach s tf
member x.Return v = value v
member x.ReturnFrom t = t : State<'T>
member x.Yield v = value v
member x.Zero () = value ()
[<CustomOperation("set", MaintainsVariableSpaceUsingBind = true)>]
member x.Set (s, k, v) = s |> combine (set k v)
end
Я использовал MaintainsVariableSpaceUsingBind
, потому что иначе он не видит v
. MaintainsVariableSpace
выдает странные ошибки, запрашивающие типы seq
, которые, как я смутно подозреваю, являются оптимизацией для вычислений, основанных на seq
. Проверка сгенерированного кода, по-видимому, делает правильную вещь, поскольку она связывает пользовательские операции вместе, используя мою функцию связывания в правильном порядке.
Теперь я готов определить вычисление state
state {
// Works fine
set "key" -1
for v in 0..2 do
// Won't work because: FS3086: A custom operation may not be used in conjunction with 'use', 'try/with', 'try/finally', 'if/then/else' or 'match' operators within this computation expression
set "hello" v
return! State.get "key"
}
К сожалению, компилятор не позволяет мне использовать пользовательские операции в условных операциях, таких как if
, try
, а также for
(хотя его нет в списке, в каком-то смысле он условный). Похоже, это намеренное ограничение. Это можно обойти, но это не так
state {
set "key" -1
for v in 0..2 do
// Meh
do! state { set "key" v }
return! State.get "key"
}
ИМХО, я предпочитаю использовать обычные do!/let!
вместо пользовательских операций:
state {
for v in 0..2 do
do! State.set "key" v
return! State.get "key"
}
Так что это не совсем правильный ответ на вопрос от OP, но, возможно, это поможет вам продвинуться немного дальше?
Полный исходный код:
type [<Struct>] State<'T> = S of (Map<string, obj> -> 'T*Map<string, obj>)
module State =
let value v = S <| fun m -> v, m
let bind uf (S t) = S <| fun m ->
let tv, tm = t m
let (S u) = uf tv
u tm
let combine u (S t) = S <| fun m ->
let _, tm = t m
let (S u) = u
u tm
let delay tf = S <| fun m ->
let (S t) = tf ()
t m
let forEach s tf = S <| fun m ->
let mutable a = m
for v in s do
let (S t) = tf v
let (), tm = t m
a <- tm
(), a
let get k : State<'T option> = S <| fun m ->
match m |> Map.tryFind k with
| Some (:? 'T as v) -> Some v, m
| _ -> None, m
let set k v = S <| fun m ->
let m = m |> Map.add k (box v)
(), m
let run (S t) m = t m
type Builder() =
class
member x.Bind (t, uf) = bind uf t
member x.Combine (t, u) = combine u t
member x.Delay tf = delay tf
member x.For (s, tf) = forEach s tf
member x.Return v = value v
member x.ReturnFrom t = t : State<'T>
member x.Yield v = value v
member x.Zero () = value ()
[<CustomOperation("set", MaintainsVariableSpaceUsingBind = true)>]
member x.Set (s, k, v) = s |> combine (set k v)
end
let state = State.Builder ()
let testUpdate () =
state {
// Works fine
set "key" -1
for v in 0..2 do
// Won't work because: FS3086: A custom operation may not be used in conjunction with 'use', 'try/with', 'try/finally', 'if/then/else' or 'match' operators within this computation expression
// set "hello" v
// Workaround but kind of meh
// do! state { set "key" v }
// Better IMHO
do! State.set "key" v
return! State.get "key"
}
[<EntryPoint>]
let main argv =
let tv, tm = State.run (testUpdate ()) Map.empty
printfn "v:%A" tv
printfn "m:%A" tm
0
person
Just another metaprogrammer
schedule
28.12.2019