Search code examples
f#monadscustom-operator

How to define custom operator in computational expression


I want to define some custom operators on my computation expression, but can't make it work

type ZipSeq() =

    [<CustomOperation("<*>")>]
    member this.Apply f s = 
        f |> Seq.zip s |> Seq.map (fun (y, x) -> x(y))

    member this.Return x = 
        Seq.initInfinite (fun _ -> x)

    // (a -> b) -> seq<a> -> seq<b>
    [<CustomOperation("<!>")>]
    member this.Map f s =
        this.Apply (this.Return f) s

let zipSeq = new ZipSeq()

let f (a : float) = a * a
let s = seq { yield 1. }

// seq<b>
let h1 = zipSeq.Map f s

//thinking h1 should be the same as h2
//but compilation error : ` This value is not a function and cannot be applied`
let h2 = zipSeq { return f <!> s }

Btw, changing member this.Map f s ... to member this.Map (f, s) ... gives same error.


Solution

  • As already mentioned in the comments, computation expressions and custom operators are two orthogonal language features that do not interact in any way. If you want to use custom operators, you can just define custom operator and use it (you can define them as members of a type to restrict their scope, or as members of a module that has to be explicitly opened).

    If you are interested in using computation expressions for something like applicative programming style, it is worth noting that you can define "zip-like" operations in computation expressions. This lets you write zipping with a nice syntax:

    zipSeq {
      for x in [1; 2; 3] do
      zip y in ['a'; 'b'; 'c'] 
      yield x, y }
    

    This produces a sequence with [1,a; 2,b; 3,c]. The computation builder definition that lets you do this looks as follows:

    type SeqZipBuilder() = 
        member x.For(ev:seq<'T>, loop:('T -> #seq<'U>)) : seq<'U> = 
          Seq.collect loop ev
        member x.Yield(v:'T) : seq<'T> = seq [v]
        [<CustomOperation("zip",IsLikeZip=true)>]
        member x.Zip
          ( outerSource:seq<'Outer>,  innerSource:seq<'Inner>,  
            resultSelector:('Outer -> 'Inner -> 'Result)) : seq<'Result> =
            Seq.map2 resultSelector outerSource innerSource
    
    let zipSeq = SeqZipBuilder()
    

    (As far as I know, this is not very well documented, but there is a bunch of examples in F# repo tests that show how zip-like (and other) custom operations can be defined.)