我正在尝试使用 FSharp 计算表达式制作构建器,但收到错误 FS0039:
type UpdatebBuilder() =
member this.Yield (x) = x
member this.Return (x) = x
member this.Bind (x, cont) = cont(x)
member this.Quote (x) = x
member this.For (x, a) = x
[<CustomOperation("set", MaintainsVariableSpace =true,AllowIntoPattern=true)>]
member this.Set (x, a, b) = x
let update = UpdatebBuilder()
let testUpdate () =
update {
for x in [| 1; 2 ; 3|] do
set x 123 // Compile Error FS0039: The value or constructor 'x' is not defined.
}
我想要实现的是类似于查询表达式的东西:
query {
for x in collection do
where x = 2 // Why no FS0039 error here?
select x
}
也尝试了MaintainsVariableSpaceUsingBind=true
,并得到同样的错误。我应该怎么做才能编译它?
对我来说,您似乎正在尝试定义一个状态 monad 并将 Set 操作实现为自定义操作。
我承认我从来没有完全了解 F# 中的自定义操作(而且我经常使用 F#(。恕我直言,感觉自定义操作有一个目的;在 F# 中启用类似 LINQ 的语法。随着时间的推移,似乎很少有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.
无论如何,为了了解有关自定义操作的更多信息,我尝试使用自定义操作来实现状态 monadset
,我走得更远,但遇到了一个问题,我认为这是编译器的故意限制。仍然认为我分享它,希望它能帮助 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