FSharp 计算表达式:无法在自定义操作中引用绑定值



我正在尝试使用 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<_>是一个函数,它给定一个全局状态(映射(产生一个值(可以从全局状态派生,但不一定(和一个可能更新的全局状态。

returnvalue我倾向于将其称为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

getset用于与全局状态交互:

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"
}

不幸的是,编译器阻止我在条件操作中使用自定义操作,例如iftryfor(即使它不在列表中,它在某种意义上是有条件的(。这似乎是一种有意的限制。可以解决它,但感觉很婉

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

相关内容

  • 没有找到相关文章