F# Custom Operator reporting incorrect usage in Computation Expression

557 Views Asked by At

I am creating a Computation Expression (CE) for simplifying the definition of Plans for modelers. I want to define functions that are only available in the CE. In this example the compiler says that the custom operations step and branch are being used incorrectly but I don't see why. All the compiler is saying is that they are not used correctly.

Note I know that I could define step and branch outside of the CE to accomplish this. This question is explicitly about using Custom Operators. I want to isolate this logic so that it is only available in the context of the 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"
        ]
    }

The error that is reported for step is

FS3095: 'step' is not used correctly. This is a custom operation in this query or computation expression.

I.e.:

Step error

2

There are 2 best solutions below

0
On BEST ANSWER

It's because you're inside of the list at this point. The CE keywords only work directly at the "top level" of a CE, as far as I'm aware.

You could make a "sub" CE for the individual step and put keywords in there e.g.

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.

3
On

In addition to Isaac Abraham's suggestion of creating a sub CE, you might consider scrapping the steps operation and redefining the step operation like this:

    [<CustomOperation "step">]
    member _.Step (state, step) =
        { state with 
            Steps = state.Steps @ [ Action step ]
        }

Which would allow you to do this:

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