gpt4 book ai didi

带有自定义运算符的 F# 嵌套计算表达式

转载 作者:行者123 更新时间:2023-12-05 09:01:24 25 4
gpt4 key购买 nike

我正在创建一个用于建模的 DSL,我希望能够创建一个具有两个自定义操作的 Settings 构建器:BufferConstraint,它们本身就是计算表达式。这样做的原因是域中的术语过多,计算表达式允许您通过使用自定义操作来提供上下文。

我不知道如何让这个嵌套按预期工作。我在代码示例的底部提供了一个示例,说明我想要的结果。

type Buffer =
{
Name : string
Capacity : float
}

type Constraint =
{
Name : string
Limit : float
}

[<RequireQualifiedAccess>]
type Setting =
| Buffer of Buffer
| Constraint of Constraint


type BufferBuilder (name: string) =
member _.Yield _ : Buffer = { Name = name; Capacity = 0.0 }
member _.Run x : Buffer = x

[<CustomOperation("Capacity")>]
member _.Capacity (b: Buffer, newCapacity) =
{ b with Capacity = newCapacity }

let Buffer = BufferBuilder

type ConstraintBuilder (name: string) =
member _.Yield _ : Constraint = { Name = name; Limit = 0.0 }
member _.Run x : Constraint = x

[<CustomOperation("Limit")>]
member _.Limit (b: Constraint, newLimit) =
{ b with Limit = newLimit }

let Constraint = ConstraintBuilder

type SettingsBuilder () =

member _.Yield _ : Setting list = []
member _.Run x : Setting list = x

[<CustomOperation("Buffer")>]
member _.Buffer (settings, name: string, expr) =
// This does not work
let newSetting = BufferBuilder name expr
newSetting :: settings

[<CustomOperation("Constraint")>]
member _.Constraint (settings, name: string, expr) =
// This does not work
let newSetting = ConstraintBuilder name expr
newSetting :: settings


// The Computation Expression does not work
let mySettings =
SettingsBuilder {
Buffer "b1" {
Capacity 100.0
}
Constraint "c1" {
Limit 10.0
}
}

// Below shows that the desired outcome of `mySettings` would be
let b1 = { Name = "b1"; Capacity = 100.0 }
let c1 = { Name = "c1"; Limit = 10.0 }

let desiredSettings = [
Setting.Buffer b1
Setting.Constraint c1
]

最佳答案

CE 不是那样工作的。当您编写 foo { ... } 时,该表达式中的 foo不是函数。特别是,这意味着您不能这样做:

let x = { ... }
let y = foo x

或者这个:

let f x = foo x
let y = f { ... }

不是这样的。它是特殊语法,不是函数调用。花括号前面的东西必须是一个 CE 对象,上面定义了所有 CE 方法。

所以特别是,这意味着您的 SettingsBuilder.Buffer 函数不能接受 expr 然后将其传递给 BufferBuilderBufferBuilder 必须紧跟在花括号的前面。

这意味着 SettingsBuilder.Buffer 函数应该接受 BufferBuilder 的任何结果,然后,在 CE 中,您应该使用 BufferBuilder 构建该结果,然后仅将其传递给 Buffer 自定义操作:

    [<CustomOperation("Buffer")>]
member _.Buffer (settings, b) =
(Setting.Buffer b) :: settings

[<CustomOperation("Constraint")>]
member _.Constraint (settings, c) =
(Setting.Constraint c) :: settings

member _.Zero () = []

...

let mySettings =
SettingsBuilder () {
Buffer (BufferBuilder "b1" {
Capacity 100.0
})
Constraint (ConstraintBuilder "c1" {
Limit 10.0
})
}

(请注意,您还必须定义 Zero 以提供表达式的“初始”值)

(还要注意 SettingsBuilder 之后的单元 ()。没有它,SettingsBuilder 就是一个类,但是你需要左边的东西花括号是一个对象)

我知道你想要像 Buffer "foo"{ ... } 这样的漂亮语法,而不是额外的 BufferBuilder 和丑陋的括号,但我不想要认为可以做到。通常,没有办法让自定义操作表现得像“嵌套”表达式。


考虑另一种方法:放弃外部 CE,而是将设置定义为列表,每个元素都使用其对应的 CE 构建。

您将需要这些内部 CE 分别生成一个 Setting,以便它们的结果可以是同一列表的元素。这可以通过修改它们的 Run 方法以将结果值包装在相关的 Setting 构造函数中来实现:

type BufferBuilder (name: string) =
...
member _.Run x = Setting.Buffer x

type ConstraintBuilder (name: string) =
...
member _.Run x = Setting.Constraint x

...

let mySettings = [
BufferBuilder "b1" {
Capacity 100.0
}
ConstraintBuilder "c1" {
Limit 10.0
}
]

关于带有自定义运算符的 F# 嵌套计算表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73225646/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com