如何在计算表达式中定义自定义运算符

时间:2016-10-10 19:48:06

标签: f# monads custom-operator

我想在我的计算表达式上定义一些自定义运算符,但不能使它工作

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 }

顺便说一句,将member this.Map f s ...更改为member this.Map (f, s) ...会产生同样的错误。

1 个答案:

答案 0 :(得分:4)

正如评论中已经提到的,计算表达式和自定义运算符是两种不以任何方式交互的正交语言特性。如果要使用自定义运算符,可以只定义自定义运算符并使用它(可以将它们定义为类型的成员以限制其范围,或者作为必须显式打开的模块的成员)。

如果您有兴趣将计算表达式用于应用程序编程风格之类的东西,那么值得注意的是可以定义&#34; zip-like&#34;计算表达式中的操作。这可以让你用一个很好的语法编写压缩文件:

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

这会生成一个[1,a; 2,b; 3,c]的序列。 允许您执行此操作的计算构建器定义如下所示:

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

(据我所知,这篇文章没有很好的记录,但F# repo tests中有很多例子说明了如何定义类似zip的(和其他)自定义操作。)