F#自定义运算符在计算表达式中报告了错误的使用方式。

16
我正在为建模者简化计划定义创建一个计算表达式(CE)。我想定义只在CE中可用的函数。在这个例子中,编译器说自定义操作stepbranch的使用是错误的,但我不明白为什么。编译器只是说它们没有被正确使用。

注意我知道我可以在CE之外定义stepbranch来实现这个。这个问题明确是关于使用自定义操作符。我想将这个逻辑隔离起来,只在CE的上下文中可用。

type Step =
    | Action of string
    | Branch of string list list

type Plan =
    {
        Name : string
        Steps : Step list
    }

type PlanBuilder () =

    member _.Yield _ =
        {
            Name = ""
            Steps = []
        }
    
    member _.Run state = state

    [<CustomOperation "name">]
    member _.Name (state, name) =
        { state with Name = name }

    [<CustomOperation "steps">]
    member _.Steps (state, steps) =
        { state with Steps = steps }

    [<CustomOperation "step">]
    member _.Step (state, step) =
        Action step

    [<CustomOperation "branch">]
    member _.Branch (state, branch) =
        Branch branch

let plan = PlanBuilder ()

let x =
    plan {
        name "Chicken"
        steps [
            // The compiler reports errors for all the 
            // `step` and `branch` calls
            step "1"
            step "2"
            branch [
                [
                    step "3a"
                    step "4a"
                ]
                [
                    step "3b"
                    step "4b"
                ]
            ]
            step "5"
        ]
    }

报告的错误是关于step的。

FS3095: 'step'的使用不正确。这是查询或计算表达式中的自定义操作。

I.e.:

Step error


2
我认为你的代码一开始就不需要使用CE。只需使用函数,这样更可预测且易于理解。在这种情况下,你唯一会失去的是在代码中重新排列“名称”和“步骤”的能力。如果你真的需要使用CE,那么你应该将“步骤”和“分支”也设为CE。 - JL0PD
3
我知道我可以只使用函数,但这不是问题的重点。 - Matthew Crews
1
请勿发布文本截图。这些截图无法被搜索、复制,甚至不能被屏幕阅读器等自适应技术的用户使用。相反,请将代码直接粘贴为文本到您的问题中。如果您选择它并单击 {} 按钮或 Ctrl+K,则代码块将缩进四个空格,从而使其呈现为代码。 - Chris
有时候无法复制粘贴内容,因此需要截图。在这种情况下,如果我移动光标以复制/粘贴内容,则内容会消失,从而无法复制文本。我的大多数问题都有格式化的代码。我只在万不得已时使用截图。 - Matthew Crews
作为一个提示:在VSCode、Rider或Visual Studio中,你也可以在错误列表中找到这个错误(可能需要将其聚焦)。只需按Ctrl-C(或右键点击,选择复制),你就可以获得错误,而无需手动输入。或者,只需编译并从输出窗口获取错误(附注:我已将错误添加到你的问题中)。 - undefined
2个回答

9

这是因为您此时在列表内部。据我所知,CE关键字只在CE的“顶层”直接起作用。

您可以为每个步骤创建一个“子”CE,并在其中放置关键字,例如:

plan {
        name "Chicken"
        steps [
            // The compiler reports errors for all the 
            // `step` and `branch` calls
            step { name "1" }
            step { name "2" }
            branch [
                [
                    step { name "3a" }
                    step { name "4a" }
                ]
            ]
        ]
    }

etc.


4
除了Isaac Abraham建议创建一个子CE之外,您可能还可以考虑放弃steps操作,并重新定义step操作如下:
    [<CustomOperation "step">]
    member _.Step (state, step) =
        { state with 
            Steps = state.Steps @ [ Action step ]
        }

这将让您能够做到这一点:

    plan {
        name "Chicken"        
        step "2"
        step "3"
        branch {
            step "3a"
            step "3b"
        }
        step "4"
        step "5"
        branch {
            step "5a"
            step "5b"
        }
    }

你如何处理类似示例中的分支和嵌套?据我理解,这不能用CEs完成。至少,不是用单个CE完成。 - Matthew Crews
1
这也是一个不错的解决方案。唯一的问题是,除非你开始在构建器内实现循环(for循环,while循环),否则无法轻松地以编程方式使用它,例如在运行时提供的列表。 - Isaac Abraham
2
我在想将step与Isaac建议的嵌套CE结合使用。我更新了我的原始代码示例以进行澄清。 - Jordan Marr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接