Как определить пользовательский оператор в вычислительном выражении

#f# #monads #custom-operator

#f# #монады #пользовательский оператор

Вопрос:

Я хочу определить некоторые пользовательские операторы в моем вычислительном выражении, но не могу заставить его работать

 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. Я не думаю, что это возможно. Хотя это было бы круто.

2. Вы должны быть в состоянии достичь этого синтаксиса, просто определяя эти операторы отдельно (т. Е. Не Как Часть построителя вычислений) и заставляя их возвращать экземпляр monad, который затем может быть let! -ed или return! -ed .

3. Не могли бы вы предложить какие-либо альтернативы?

4. @baio Пожалуйста, постарайтесь свести к минимуму редактирование ваших комментариев. После того, как вы отредактировали свой комментарий выше, мой ответ больше не имеет смысла.

5. На практике это случается редко. И когда это происходит, потребитель контролирует, какие модули они открывают, а какие нет, и они могут легко переименовать некоторые операторы, если им действительно нужно использовать оба. Но вы правы: операторы на самом деле редко бывают хорошей идеей. Вам следует избегать создания собственных операторов, и когда вы уверены, что хотите это сделать, вы должны поместить их в отдельный модуль.

Ответ №1:

Как уже упоминалось в комментариях, вычислительные выражения и пользовательские операторы — это две ортогональные языковые функции, которые никак не взаимодействуют. Если вы хотите использовать пользовательские операторы, вы можете просто определить пользовательский оператор и использовать его (вы можете определить их как члены типа, чтобы ограничить их область действия, или как члены модуля, который должен быть явно открыт).

Если вы заинтересованы в использовании вычислительных выражений для чего-то вроде прикладного стиля программирования, стоит отметить, что вы можете определять «zip-подобные» операции в вычислительных выражениях. Это позволяет вам записывать архивирование с хорошим синтаксисом:

 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 есть множество примеров, которые показывают, как можно определить zip-подобные (и другие) пользовательские операции.)