2014-04-17 20 views
12

Gittikçe bir değer oluşturan ve birçok özel işlem içeren bir hesaplama ifadesi oluşturucu var. Ancak, standart F # dil yapılarına izin vermez ve bu desteği nasıl ekleyeceğimi bulmakta çok fazla sorun yaşıyorum.Bir değer biriktiren ve standart dil yapılarına izin veren bir hesaplama ifadesi oluşturucu nasıl yazılır?

tek başına bir örnek vermek gerekirse, burada F # listelerini oluşturur bir ölü basit ve oldukça anlamsız hesaplama deyim:

Ben sadece iyi listeleri oluşturmak için kullanabilir
type Items<'a> = Items of 'a list 

type ListBuilder() = 
    member x.Yield(()) = Items [] 

    [<CustomOperation("add")>] 
    member x.Add(Items current, item:'a) = 
     Items [ yield! current; yield item ] 

    [<CustomOperation("addMany")>] 
    member x.AddMany(Items current, items: seq<'a>) = 
     Items [ yield! current; yield! items ] 

let listBuilder = ListBuilder() 

let build (Items items) = items 

:

let stuff = 
    listBuilder { 
     add 1 
     add 5 
     add 7 
     addMany [ 1..10 ] 
     add 42 
    } 
    |> build 

Ancak bu bir derleyici hatadır:

listBuilder { 
    let x = 5 * 39 
    add x 
} 

// This expression was expected to have type unit, but 
// here has type int. 

Ve böylece şudur:

listBuilder { 
    for x = 1 to 50 do 
     add x 
} 

// This control construct may only be used if the computation expression builder 
// defines a For method. 

Bulduğum tüm belgeleri ve örnekleri okudum, ancak almadığım bir şey var. Her bir .Bind() veya .For() yöntem imzası Ben sadece daha fazla kafa karıştırıcı derleyici hataları neden olur deneyin. Bulunduğum örneklerin çoğu ya giderken bir değer oluşturuyor ya da normal F # dil yapılarına izin veriyor, ancak her ikisini de yapamadım.

birisi nasıl bu örneği alıp minimumda (let bağlamaları ve for döngüler için oluşturucudaki desteği eklemek için bana göstererek doğru yönde işaret olsaydı - çok iyi olurdu using, while ve try/catch ama muhtemelen yapabiliriz Birisi beni başlatırsa, bunu anlayın) o zaman dersi minnetle benim asıl sorunuma uygulayabileceğim. Gördüğüm

cevap

10

bakmak için en iyi yer spec olduğunu. Örneğin,

b { 
    let x = e 
    op x 
} 

şeyler yanlış gitmiş nerede bariz bir çözüm sunmaz gerçi Yani bu, gösterir

T(let x = e in op x, [], fun v -> v, true) 
=> T(op x, {x}, fun v -> let x = e in v, true) 
=> [| op x, let x = e in b.Yield(x) |]{x} 
=> b.Op(let x = e in in b.Yield(x), x) 

için tercüme. Açıkça, Yield'un genelleştirilmiş olması gerekir, çünkü rasgele komutlar almalıdır (kapsamda kaç değişkenin bulunduğuna bağlı olarak). Belki daha incelikle, xadd çağrı kapsamı içinde olmadığını gösterir (b.Op ikinci bağımsız değişken olarak ilişkisiz x bakın?). Özel işleçlerinizin bağlı değişkenleri kullanmasına izin vermek için, argümanlarının [<ProjectionParameter>] özniteliğine sahip olması gerekir (ve argüman olarak isteğe bağlı değişkenlerden işlevler alırsınız) ve ayrıca, ilişkili değişkenlerin daha sonra kullanılabilir olmasını istiyorsanız MaintainsVariableSpace değerini true olarak ayarlamanız gerekir. operatörler. ben yanlış kanıtlanmış isteriz olsa (yanında ve her operasyon gelen ciltli değerler kümesini geçen önlemek için yolu yoktur görünüyor, bu kadar Bina

b.Op(let x = e in b.Yield(x), fun x -> x) 

: Bu nihai çevirisini değişecek) - Bu değerleri en sonunda geri almak için Run yöntemini eklemeniz gerekir.Hepsini bir araya getirirsek, bu gibi görünen bir oluşturucu alırsınız: "eğlenceli ve kar için F #" de

type ListBuilder() = 
    member x.Yield(vars) = Items [],vars 

    [<CustomOperation("add",MaintainsVariableSpace=true)>] 
    member x.Add((Items current,vars), [<ProjectionParameter>]f) = 
     Items (current @ [f vars]),vars 

    [<CustomOperation("addMany",MaintainsVariableSpace=true)>] 
    member x.AddMany((Items current, vars), [<ProjectionParameter>]f) = 
     Items (current @ f vars),vars 

    member x.Run(l,_) = l 
+0

Açıklama için teşekkürler! Kaybettiğim ana şey, taşınan değeri geçerli değerden ve 'ProjectionParameter' özniteliğinden çıkarmak için 'Çalıştır' kaynağı sağlamaktı. 'For' ve diğer oluşturucu yöntemleri üzerine yazılan belgeler, derleyicinin herşeyin 'birim' olmasını beklemediğinden artık çok daha mantıklı olacak. –

+0

Bir [aşağıdaki soruyu burada yayınladım] (http://stackoverflow.com/questions/23144744/why-does-this-computation-expression-builder-expect-unit-in-my-for-loop) Yine daha az aptal olmana yardım edecek zamanın var. –

+0

@kvb, CE'yi elinizle desugaring ediyorsanız veya * yazmıyor olsa bile genişletmeyi görmenin bir yolu var mı *? –

3

en eksiksiz örnekleri §6.3.10 of the spec özellikle bu biridir:

/// Computations that can cooperatively yield by returning a continuation 
type Eventually<'T> = 
    | Done of 'T 
    | NotYetDone of (unit -> Eventually<'T>) 

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] 
module Eventually = 

    /// The bind for the computations. Stitch 'k' on to the end of the computation. 
    /// Note combinators like this are usually written in the reverse way, 
    /// for example, 
    ///  e |> bind k 
    let rec bind k e = 
     match e with 
     | Done x -> NotYetDone (fun() -> k x) 
     | NotYetDone work -> NotYetDone (fun() -> bind k (work())) 

    /// The return for the computations. 
    let result x = Done x 

    type OkOrException<'T> = 
     | Ok of 'T 
     | Exception of System.Exception      

    /// The catch for the computations. Stitch try/with throughout 
    /// the computation and return the overall result as an OkOrException. 
    let rec catch e = 
     match e with 
     | Done x -> result (Ok x) 
     | NotYetDone work -> 
      NotYetDone (fun() -> 
       let res = try Ok(work()) with | e -> Exception e 
       match res with 
       | Ok cont -> catch cont // note, a tailcall 
       | Exception e -> result (Exception e)) 

    /// The delay operator. 
    let delay f = NotYetDone (fun() -> f()) 

    /// The stepping action for the computations. 
    let step c = 
     match c with 
     | Done _ -> c 
     | NotYetDone f -> f() 

    // The rest of the operations are boilerplate. 

    /// The tryFinally operator. 
    /// This is boilerplate in terms of "result", "catch" and "bind". 
    let tryFinally e compensation = 
     catch (e) 
     |> bind (fun res -> compensation(); 
          match res with 
          | Ok v -> result v 
          | Exception e -> raise e) 

    /// The tryWith operator. 
    /// This is boilerplate in terms of "result", "catch" and "bind". 
    let tryWith e handler = 
     catch e 
     |> bind (function Ok v -> result v | Exception e -> handler e) 

    /// The whileLoop operator. 
    /// This is boilerplate in terms of "result" and "bind". 
    let rec whileLoop gd body = 
     if gd() then body |> bind (fun v -> whileLoop gd body) 
     else result() 

    /// The sequential composition operator 
    /// This is boilerplate in terms of "result" and "bind". 
    let combine e1 e2 = 
     e1 |> bind (fun() -> e2) 

    /// The using operator. 
    let using (resource: #System.IDisposable) f = 
     tryFinally (f resource) (fun() -> resource.Dispose()) 

    /// The forLoop operator. 
    /// This is boilerplate in terms of "catch", "result" and "bind". 
    let forLoop (e:seq<_>) f = 
     let ie = e.GetEnumerator() 
     tryFinally (whileLoop (fun() -> ie.MoveNext()) 
           (delay (fun() -> let v = ie.Current in f v))) 
        (fun() -> ie.Dispose()) 


// Give the mapping for F# computation expressions. 
type EventuallyBuilder() = 
    member x.Bind(e,k)     = Eventually.bind k e 
    member x.Return(v)     = Eventually.result v 
    member x.ReturnFrom(v)    = v 
    member x.Combine(e1,e2)    = Eventually.combine e1 e2 
    member x.Delay(f)     = Eventually.delay f 
    member x.Zero()      = Eventually.result() 
    member x.TryWith(e,handler)   = Eventually.tryWith e handler 
    member x.TryFinally(e,compensation) = Eventually.tryFinally e compensation 
    member x.For(e:seq<_>,f)   = Eventually.forLoop e f 
    member x.Using(resource,e)   = Eventually.using resource e 
İlgili konular