Extended computation expressions without for..in..do

后端 未结 2 751
谎友^
谎友^ 2021-01-31 04:10

What I mean by extended computation expressions is computation expressions with custom keywords defined via CustomOperation attribute.

When reading about extend

2条回答
  •  失恋的感觉
    2021-01-31 04:56

    I have to admit I do not fully understand how computation expressions work when you use query expression features like the CustomOperation attribute. But here are some remarks from some my experiments that might help....

    Firstly, I think it is not possible to freely combine standard computation expression features (return! etc.) with custom operations. Some combinations are apparently allowed, but not all. For example, if I define custom operation left and return! then I can only use the custom operation before return!:

    // Does not compile              // Compiles and works
    moves { return! lr               moves { left 
            left }                           return! lr }
    

    As for the computations that use only custom operations, most common cusotom operations (orderBy, reverse and this kind) have a type M<'T> -> M<'T> where M<'T> is some (possibly generic) type that represent the thing we're building (e.g. a list).

    For example, if we want to build a value that represents a sequence of left/right moves, we can use the following Commands type:

    type Command = Left | Right 
    type Commands = Commands of Command list
    

    Custom operations like left and right can then transform Commands into Commands and append the new step to the end of the list. Something like:

    type MovesBuilder() =
      []
      member x.Left(Commands c) = Commands(c @ [Left])
      []
      member x.Right(Commands c) = Commands(c @ [Right])
    

    Note this is different from yield which returns just a single operation - or command - and so yield needs Combine to combine multiple individual steps if you use custom operations, then you never need to combine anything because the custom operations gradually build the Commands value as a whole. It only needs some initial empty Commands value that is used at the beginning...

    Now, I would expect to see Zero there, but it actually calls Yield with unit as an argument, so you need:

    member x.Yield( () ) = 
      Commands[]
    

    I'm not sure why this is the case, but Zero is quite often defined as Yield (), so perhaps the goal is to use the default definition (but as I said, I'd also expect to use Zero here...)

    I think combining custom operations with computation expressions makes sense. While I have strong opinions on how standard computation expressions should be used, I do not really have any good intuition about computations with custom operations - I think the community still needs to figure this out :-). But for example, you can extend the above computation like this:

    member x.Bind(Commands c1, f) = 
      let (Commands c2) = f () in Commands(c1 @ c2)
    member x.For(c, f) = x.Bind(c, f)
    member x.Return(a) = x.Yield(a)
    

    (At some point, the translation will start requiring For and Return, but here they can be defined just like Bind and Yield - and I do not fully understand when is which alternative used).

    Then you can write something like:

    let moves = MovesBuilder()
    
    let lr = 
      moves { left
              right }    
    let res =
      moves { left
              do! lr
              left 
              do! lr }
    

提交回复
热议问题