1
votes

This code runs fine except if I uncomment the last line in my custom seq expression :

type T (i: int) = 
    member x.i = i
    override x.ToString() =
        sprintf "T is %A " x.i

type TBuilder() =
    member x.Yield (()) = Seq.empty

    [<CustomOperation("test")>]
    member x.Test1 (source : seq<_>, i: int) : seq<T> =
        printfn "Calling Test1 with i= %d" i |> ignore
        seq { yield! source
              yield T(i) }

let t = TBuilder()

let mytest = 
    t {
        test 42
        test 43
//        yield T(44)  // if uncommented, it does not compile
    }

If yield T(44) line is uncommented, I get an compiler error like so :

Error   This control construct may only be used if the computation expression builder defines a 'For' method.

My question : Is there a way to mix

  • my [CustomOperation] test (from method Test1) that yields T objects

with

  • a vanilla yield, for example yield T(44) or any other seq related syntax

inside a unique seq expression BUT without defining any 'For' method ?

Reference : DSL in Action for F# (Chapter 7) by Anh-Dung Phan on github. Thanks.

1
Why your Yield member takes a unit? I'd assume it should take T value. - Bartek KobyƂecki
@Bartek ; Yield was defined like this in the reference cited. It returns an empty seq to initialize a seq and then Test1 method gets all the source yielded so far and adds another element. - FZed

1 Answers

1
votes

Short answer: no. If you change your operators so that they preserve variable bindings (via MaintainsVariableSpace=true or MaintainsVariableSpaceUsingBind=true arguments to the [<CustomOperator>] attribute constructor) then you won't need For but you'll need Bind instead.

What do you expect the computation expression you've written to mean? If you look at how the F# spec specifies the translation for computation expressions, anything of the form

bldr {
  op1 x
  op2 y
  yield z
}

will turn into something like

bldr.For(bldr.Op2(bldr.Op1(bldr.Yield(), x), y), fun () -> b.Yield(z))

so you clearly need a For method and also your Yield method needs to do something different; at the very least it needs to be able to take arguments of arbitrary types (e.g. in the above example it needs to work on an argument of type unit and also on an argument of whatever type the value z has).