F# 我如何编写一个计算表达式生成器来累积一个值并允许标准语言构造?
我有一个计算表达式生成器,它可以在运行时构建一个值,并且有许多自定义操作。然而,它不允许标准的F#语言构造,我在弄清楚如何添加这种支持方面遇到了很多困难 举一个独立的例子,这里有一个非常简单且毫无意义的计算表达式,用于构建F#列表: 但是,这是一个编译器错误:F# 我如何编写一个计算表达式生成器来累积一个值并允许标准语言构造?,f#,computation-expression,F#,Computation Expression,我有一个计算表达式生成器,它可以在运行时构建一个值,并且有许多自定义操作。然而,它不允许标准的F#语言构造,我在弄清楚如何添加这种支持方面遇到了很多困难 举一个独立的例子,这里有一个非常简单且毫无意义的计算表达式,用于构建F#列表: 但是,这是一个编译器错误: listBuilder { let x = 5 * 39 add x } // This expression was expected to have type unit, but // here has type i
listBuilder {
let x = 5 * 39
add x
}
// This expression was expected to have type unit, but
// here has type int.
这也是:
listBuilder {
for x = 1 to 50 do
add x
}
// This control construct may only be used if the computation expression builder
// defines a For method.
我已经阅读了我能找到的所有文档和示例,但有些东西我没有理解。我尝试的每个.Bind()
或.For()
方法签名都会导致越来越多令人困惑的编译器错误。我能找到的大多数例子要么是在你进行的过程中建立一个值,要么是允许常规的F#语言构造,但我还没有找到一个两者都能做到的例子
如果有人能给我指出正确的方向,告诉我如何使用这个例子,并在生成器中添加对
的支持,让绑定和循环(至少使用,while
和try/catch
会很好,但如果有人让我开始的话,我可能会找到这些)然后,我将能够感激地将这一课应用到我的实际问题中。我所看到的最完整的例子在,尤其是这一个:
/// Computations that can cooperatively yield by returning a continuation
type Eventually<'T> =
| Done of 'T
| NotYetDone of (unit -> Eventually<'T>)
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Eventually =
/// The bind for the computations. Stitch 'k' on to the end of the computation.
/// Note combinators like this are usually written in the reverse way,
/// for example,
/// e |> bind k
let rec bind k e =
match e with
| Done x -> NotYetDone (fun () -> k x)
| NotYetDone work -> NotYetDone (fun () -> bind k (work()))
/// The return for the computations.
let result x = Done x
type OkOrException<'T> =
| Ok of 'T
| Exception of System.Exception
/// The catch for the computations. Stitch try/with throughout
/// the computation and return the overall result as an OkOrException.
let rec catch e =
match e with
| Done x -> result (Ok x)
| NotYetDone work ->
NotYetDone (fun () ->
let res = try Ok(work()) with | e -> Exception e
match res with
| Ok cont -> catch cont // note, a tailcall
| Exception e -> result (Exception e))
/// The delay operator.
let delay f = NotYetDone (fun () -> f())
/// The stepping action for the computations.
let step c =
match c with
| Done _ -> c
| NotYetDone f -> f ()
// The rest of the operations are boilerplate.
/// The tryFinally operator.
/// This is boilerplate in terms of "result", "catch" and "bind".
let tryFinally e compensation =
catch (e)
|> bind (fun res -> compensation();
match res with
| Ok v -> result v
| Exception e -> raise e)
/// The tryWith operator.
/// This is boilerplate in terms of "result", "catch" and "bind".
let tryWith e handler =
catch e
|> bind (function Ok v -> result v | Exception e -> handler e)
/// The whileLoop operator.
/// This is boilerplate in terms of "result" and "bind".
let rec whileLoop gd body =
if gd() then body |> bind (fun v -> whileLoop gd body)
else result ()
/// The sequential composition operator
/// This is boilerplate in terms of "result" and "bind".
let combine e1 e2 =
e1 |> bind (fun () -> e2)
/// The using operator.
let using (resource: #System.IDisposable) f =
tryFinally (f resource) (fun () -> resource.Dispose())
/// The forLoop operator.
/// This is boilerplate in terms of "catch", "result" and "bind".
let forLoop (e:seq<_>) f =
let ie = e.GetEnumerator()
tryFinally (whileLoop (fun () -> ie.MoveNext())
(delay (fun () -> let v = ie.Current in f v)))
(fun () -> ie.Dispose())
// Give the mapping for F# computation expressions.
type EventuallyBuilder() =
member x.Bind(e,k) = Eventually.bind k e
member x.Return(v) = Eventually.result v
member x.ReturnFrom(v) = v
member x.Combine(e1,e2) = Eventually.combine e1 e2
member x.Delay(f) = Eventually.delay f
member x.Zero() = Eventually.result ()
member x.TryWith(e,handler) = Eventually.tryWith e handler
member x.TryFinally(e,compensation) = Eventually.tryFinally e compensation
member x.For(e:seq<_>,f) = Eventually.forLoop e f
member x.Using(resource,e) = Eventually.using resource e
///可以通过返回一个延续来协同生成的计算
类型最终=
|当然可以
|系统异常
///计算的陷阱。试缝
///计算并返回作为OkOrException的总体结果。
让rec抓住e=
匹配
|完成x->结果(确定x)
|不做任何工作->
NotYetDone(乐趣()->
让res=使用| e->Exception e尝试Ok(work())
匹配
|Ok cont->catch cont//note,tailcall
|异常e->结果(异常e))
///延迟运算符。
让延迟f=NotYetDone(fun()->f())
///计算的步进动作。
让我们来看看步骤c=
匹配c
|完成->c
|NotYetDone f->f()
//其余的操作都是样板。
///tryFinally操作员。
///这是“结果”、“捕获”和“绑定”方面的样板。
让tryFinally e补偿=
捕获(e)
|>绑定(fun res->compensation();
匹配
|Ok v->result v
|异常e->上升e)
///与接线员的通话。
///这是“结果”、“捕获”和“绑定”方面的样板。
让tryWith e handler=
抓住e
|>绑定(函数Ok v->结果v |异常e->处理程序e)
///whileLoop操作符。
///这是“结果”和“绑定”方面的样板。
let rec whileLoop gd body=
如果gd()那么body |>bind(fun v->whileLoop gd body)
其他结果()
///序列合成算子
///这是“结果”和“绑定”方面的样板。
让组合e1 e2=
e1 |>bind(fun()->e2)
///正在使用的运算符。
让我们使用(资源:#System.IDisposable)f=
tryFinally(f resource)(fun()->resource.Dispose())
///forLoop操作符。
///这是“捕获”、“结果”和“绑定”方面的样板。
设forLoop(e:seq)f=
设ie=e.GetEnumerator()
最后尝试(whileLoop(fun()->ie.MoveNext())
(延迟(fun()->设v=即f v中的电流)))
(乐趣()->即处置())
//给出F#计算表达式的映射。
键入EventuallyBuilder()=
成员x.Bind(e,k)=finally.Bind k
成员x.Return(v)=最终结果v
成员x.ReturnFrom(v)=v
成员x.Combine(e1,e2)=finally.Combine e1 e2
成员x.延迟(f)=最终延迟f
成员x.Zero()=finally.result()
成员x.trywhith(e,handler)=finally.trywhith e handler
成员x.TryFinally(e,补偿)=最终.TryFinally e补偿
成员x.For(e:seq,f)=finally.forLoop e f
成员x.Using(resource,e)=最终使用资源e
最好看的地方是。比如说,
b {
let x = e
op x
}
被翻译成
T(let x = e in op x, [], fun v -> v, true)
=> T(op x, {x}, fun v -> let x = e in v, true)
=> [| op x, let x = e in b.Yield(x) |]{x}
=> b.Op(let x = e in in b.Yield(x), x)
因此,这表明了哪里出了问题,尽管它并没有给出一个明显的解决方案。显然,Yield
需要通用化,因为它需要接受任意元组(基于范围内的变量数量)。也许更微妙的是,它还表明x
不在add
调用的范围内(请参见unboundx
作为b.Op
的第二个参数)。要允许自定义运算符使用绑定变量,其参数需要具有[]
属性(并将任意变量中的函数作为参数),如果希望绑定变量可供以后的运算符使用,还需要将maintansvariablespace
设置为true
。这将最终翻译为:
b.Op(let x = e in b.Yield(x), fun x -> x)
在此基础上,似乎无法避免在每个操作之间传递绑定值集(尽管我希望被证明是错误的)——这将要求您添加一个Run
方法,以在最后去掉这些值。将所有这些放在一起,您将得到一个如下所示的构建器:
type ListBuilder() =
member x.Yield(vars) = Items [],vars
[<CustomOperation("add",MaintainsVariableSpace=true)>]
member x.Add((Items current,vars), [<ProjectionParameter>]f) =
Items (current @ [f vars]),vars
[<CustomOperation("addMany",MaintainsVariableSpace=true)>]
member x.AddMany((Items current, vars), [<ProjectionParameter>]f) =
Items (current @ f vars),vars
member x.Run(l,_) = l
类型ListBuilder()=
成员x.收益率(变量)=项目[],变量
[]
成员x.Add((当前项目,变量),[]f)=
项目(当前@[f变量]),变量
[]
成员x.AddMany((当前项目,变量),[]f)=
项目
b.Op(let x = e in b.Yield(x), fun x -> x)
type ListBuilder() =
member x.Yield(vars) = Items [],vars
[<CustomOperation("add",MaintainsVariableSpace=true)>]
member x.Add((Items current,vars), [<ProjectionParameter>]f) =
Items (current @ [f vars]),vars
[<CustomOperation("addMany",MaintainsVariableSpace=true)>]
member x.AddMany((Items current, vars), [<ProjectionParameter>]f) =
Items (current @ f vars),vars
member x.Run(l,_) = l
module scratch
type Dispatcher = unit -> unit
type viewElement = int
type lazyViews = Lazy<list<viewElement>>
type ViewElementsBuilder() =
member x.Return(views: lazyViews) : list<viewElement> = views.Value
member x.Yield(v: viewElement) : list<viewElement> = [v]
member x.ReturnFrom(viewElements: list<viewElement>) = viewElements
member x.Zero() = list<viewElement>.Empty
member x.Combine(listA:list<viewElement>, listB: list<viewElement>) = List.concat [listA; listB]
member x.Delay(f) = f()
member x.For(coll:seq<'a>, forBody: 'a -> list<viewElement>) : list<viewElement> =
// seq {for v in coll do yield! f v} |> List.ofSeq
Seq.map forBody coll |> Seq.collect id |> List.ofSeq
let ve = new ViewElementsBuilder()
let makeComponent(m: int, dispatch: Dispatcher) : viewElement = m
let makeComponents() : list<viewElement> = [77; 33]
let makeViewElements() : list<viewElement> =
let model = {| Scores = [33;23;22;43;] |> Seq.ofList; Trainer = "John" |}
let d:Dispatcher = fun() -> () // Does nothing here, but will be used to raise messages from UI
ve {
for score in model.Scores do
yield makeComponent (score, d)
yield makeComponent (score * 100 / 50 , d)
if model.Trainer = "John" then
return lazy
[ makeComponent (12, d)
makeComponent (13, d)
]
else
return lazy
[ makeComponent (14, d)
makeComponent (15, d)
]
yield makeComponent (33, d)
return! makeComponents()
}