15

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 Step error

Matthew Crews
  • 4,105
  • 7
  • 33
  • 57
  • 2
    I don't think your code requires CE in first place. Just use functions, it's more predictable and easy to understand. Only thing you'll lose in this case is ability to rearrange `name` and `step` in code. If you really need to use CE, then you should make `steps` and `branch` CE too – JL0PD Apr 16 '22 at 13:27
  • 2
    I know I can just use functions but that’s not what the question is about. – Matthew Crews Apr 16 '22 at 13:28
  • [Please don't post screenshots of text](https://meta.stackoverflow.com/a/285557/354577). They can't be searched or copied, or even consumed by users of adaptive technologies like screen readers. Instead, paste the code as text directly into your question. If you select it and click the `{}` button or Ctrl+K the code block will be indented by four spaces, which will cause it to be rendered as code. – ChrisGPT was on strike May 11 '22 at 21:16
  • It is not always possible to copy and paste the content therefore a screenshot is necessary. In this case, if I moved the cursor to copy/paste the content disappears making it impossible to copy the text. Most of my questions have formatted code. I only use screenshots as a last resort. – Matthew Crews May 12 '22 at 19:15

2 Answers2

8

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.

Isaac Abraham
  • 3,422
  • 2
  • 23
  • 26
3

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"
        }
    }
Jordan Marr
  • 107
  • 3
  • How would you deal with branches and nesting like in the example? From my understanding, that can’t be done with CEs. At least, not with a single CE – Matthew Crews Apr 16 '22 at 15:42
  • 1
    This is a nice solution too. The only problem is that you can't easily programmatically use that e.g. a list that's supplied at runtime - unless you start implementing the while shebang (for loops, while loops) inside the builder. – Isaac Abraham Apr 16 '22 at 15:43
  • 2
    I was imagining using `step` in conjunction with Isaac's suggestion of using a nested CE. I updated my original code example to clarify. – Jordan Marr Apr 16 '22 at 17:33