2

I want to define some custom operators on my computational 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.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
baio
  • 1,102
  • 2
  • 12
  • 20
  • 1
    I don't think it's possible. It would be cool, though. – Tarmil Oct 10 '16 at 19:52
  • You should be able to achieve this syntax by just defining these operators separately (i.e. not as part of the computation builder), and make them return a monad instance, which could then be `let!`-ed or `return!`-ed. – Fyodor Soikin Oct 10 '16 at 20:02
  • Could you suggest any alternatives ? – baio Oct 10 '16 at 20:02
  • @FyodorSoikin this one actually works `let (<!>) f s = zipSeq.Map f s ;; let h3 = f <!> s` – baio Oct 10 '16 at 20:08
  • Operators should be defined with curried syntax: `let (<!>) f s = ` – Fyodor Soikin Oct 10 '16 at 20:11
  • 2
    @baio Please try to minimize editing of your comments. After you've edited your comment above, my response no longer makes sense. – Fyodor Soikin Oct 10 '16 at 20:14
  • Any case it would be preferable to somehow restrict scope for these operators (via ComputationalExpression for example) – baio Oct 10 '16 at 20:20
  • Why would it be preferable? You'll just limit code reuse that way, won't achieve anything useful. – Fyodor Soikin Oct 10 '16 at 20:27
  • What if another monads will define same operators ? And in code used both ? `zipSeq { f <!> x ....} ;; ... maybe { f <!> x ... }` – baio Oct 10 '16 at 20:40
  • 3
    In practice this rarely happens. And when it does, the consumer controls which modules they open and which they do not, and they can easily rename some operators if they really-really need to use both. But you're right: operators are rarely a good idea actually. You should avoid creating your own operators, and when you're sure you want to do it, you should put them in a separate module. – Fyodor Soikin Oct 10 '16 at 20:52

1 Answers1

5

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.)

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553