2015-06-13 7 views
6

ile geçen şeffaf devlet her zamanki MaybeBuilder kullanarak bir ağ akışından (örneğin, görüntü verilerini) bir olasılıkla eksik veri okumak için denemek Aşağıdaki kodu vardır:F # hesaplama ifadesi Bind

let image = maybe { 
    let pos = 2 //Initial position skips 2 bytes of packet ID 
    let! width, pos = readStreamAsInt 2 pos 
    let! height, pos = readStreamAsInt 2 pos 
    let! data, pos = readStream (width*height) pos 
    advanceInStream pos 
    return {width = width; height = height; pixels = data} 
} 

Yani, readStream [ asInt] [numBytes] [offset] işlevi, bir NetworkStream'de henüz veri gelmemişse, bazı [data] veya None değerini döndürür. advanceInStream işlevi tüm ağ paketi okunduğunda çalıştırılır.

Kullanıcıdan pos geçişini gizlemek için bazı özel hesaplama ifadesi oluşturucu yazmanın bir yolu olup olmadığını merak ediyorum, çünkü her zaman aynıdır - Akıştaki bazı verileri ve konumu okurum ve bir sonraki okuma işlevine iletirim son parametre.

P.S. MaybeBuilder kullandı:

type MaybeBuilder() =  
    member x.Bind(d,f) = Option.bind f d 
    member x.Return d = Some d 
    member x.ReturnFrom d = d 
    member x.Zero() = None 
let maybe = new MaybeBuilder() 

P.P.S

ikinci ben okumada veya "ise" döngüler "için" nedeniyle olası, pos kesilebilir yapmak sahip görünüyor düşünce üzerinde. Basit izin! pos Bind gölgeleme ile çalışır, ancak bir döngü içinde okuma eklerseniz, değişmezlik üzerinde tutamazsınız değil mi? Görev o zaman önemsizleşir.

+0

Evet bu bir hesaplama ifadesi olarak yazmayı gerçekten mümkün. Ayrıştırıcı hesaplama ifadeleri tanımlanırken (dizede konumun izlenmesi gerekenler) yaygındır. – FuleSnabel

+3

Çözülmesi gereken iki problem vardır: (1) 'pos' ile çalışmak' State' hesaplama ifadesi için geçerli bir iş gibi görünürken (2) 'Seçenek <'T>' döndüren işlevlerle çalışmak '' için bir iştir comp.expression, aynen yaptığınız gibi. En büyük sorun, F # 'daki Hesaplama ifadelerinin iyi bir şekilde birleştirilmemesidir **, örneğin, bir veya daha fazlasına sahip olabilirsiniz, ancak aynı anda iki tane elde etmek için, kendi özel comp.expression'unuzu yazmanız gerekir. her iki şeyi de yap. Öğrenme amaçları için iyidir, ancak gerçek hayattaki projelerde desteklenmesi zor görünebilir. – bytebuster

cevap

3

@bytebuster, özel hesaplama ifadeleriyle ilgili iyi noktalara dikkat çekiyor, ancak yine de State ve Maybe monad'ın bir arada nasıl birleştirileceğini gösterdiğimi düşünüyorum.

"Geleneksel" dillerde, tamsayılar gibi değerler oluşturmak için iyi bir desteğe sahibiz, ancak parsers geliştirirken sorunla karşılaşırız (Bir ikili akıştan değer üretme aslında ayrışıyor). Ayrıştırıcılar için, basit ayrıştırıcı işlevlerini daha karmaşık ayrıştırıcı işlevlerine dönüştürmek istiyoruz, ancak burada "geleneksel" diller genellikle iyi bir destek içermiyor.

İşlevsel dillerdeki işlevler, değerler kadar sıradan değerlerdir ve değerler oluşturulabilir olduğundan, işlevler de olabilir.

Önce bir StreamReader işlevini tanımlayalım. Bir StreamReader, bir StreamPosition (akış + konumu) alır ve güncelleştirilmiş bir StreamPosition ve bir StreamReaderResult (okuma değeri veya bir hata) üretir.

type StreamReader<'T> = 
    StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>) 

(Bu en önemli adımdır.)

Biz daha karmaşık olanları içine basit StreamReader fonksiyonlarını oluşturabilecektir ister. Bakımını yapmak istediğimiz çok önemli bir özellik, kompozisyonun StreamReader altında "kapalı" olması ve kompozisyonun sonucu StreamReader olan ve bu sayede sonsuz bir şekilde oluşturulabilmesidir.

Bir resmi okumak için genişliği & yüksekliğinden okumalı, ürünü hesaplamalı ve baytları okumalıyız. Böyle bir şey: Çünkü kompozisyonun

let readImage = 
    reader { 
    let! width = readInt32 
    let! height = readInt32 
    let! bytes = readBytes (width*height) 

    return width, height, bytes 
    } 

readImage kapatılıyor bir StreamReader<int*int*byte[]> olduğunu.biz StreamReader için operasyon Return ve Bind tanımlamanız gerekir bunu yapmadan önce amacıyla

bir hesaplama ifadesini tanımlamak gerekir yukarıdaki gibi StreamReader oluşturabilecektir ancak. Yield'un da olması iyi bir şey. StreamReader verilen değeri dönmelidir ve StreamPosition güncellemezsem olarak

module StreamReader = 
    let Return v : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     sp, (Success v) 

    let Bind (StreamReader t) (fu : 'T -> StreamReader<'U>) : StreamReader<'U> = 
    StreamReader <| fun sp -> 
     let tsp, tr = t sp 
     match tr with 
     | Success tv -> 
     let (StreamReader u) = fu tv 
     u tsp 
     | Failure tfs -> tsp, Failure tfs 

    let Yield (ft : unit -> StreamReader<'T>) : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     let (StreamReader t) = ft() 
     t sp 

Return önemsiz olduğunu.

Bind biraz daha zorlayıcıdır, ancak iki StreamReader işlevinin yeni bir tanıma nasıl yazılacağını açıklar. Bind ilk StreamReader işlevini çalıştırır ve sonucu denetler, bir hata olursa bir hata verirse aksi durumda StreamReader sonucunu kullanarak ikinci StreamReader sonucunu kullanır ve bunu güncelleme akışı konumunda çalıştırır.

Yield sadece StreamReader işlevini oluşturur ve çalıştırır. Yield, hesaplama ifadeleri oluştururken F # tarafından kullanılır.

Son olarak en biz StreamReader fonksiyonlarını birleştiren temel oluşturmuştur Şimdi hesaplama ifade oluşturucu

type StreamReaderBuilder() = 
    member x.Return v = StreamReader.Return v 
    member x.Bind(t,fu) = StreamReader.Bind t fu 
    member x.Yield(ft) = StreamReader.Yield ft 

let reader = StreamReaderBuilder() 

yapalım. Ayrıca, ilkel StreamReader işlevlerini tanımlamamız gerekir.

için tam bir örnek:

open System 
open System.IO 

// The result of a stream reader operation is either 
// Success of value 
// Failure of list of failures 
type StreamReaderResult<'T> = 
    | Success of 'T 
    | Failure of (string*StreamPosition) list 

and StreamPosition = 
    { 
    Stream : byte[] 
    Position : int 
    } 

    member x.Remaining = max 0 (x.Stream.Length - x.Position) 

    member x.ReadBytes (size : int) : StreamPosition*StreamReaderResult<byte[]> = 
    if x.Remaining < size then 
     x, Failure ["EOS", x] 
    else 
     let nsp = StreamPosition.New x.Stream (x.Position + size) 
     nsp, Success (x.Stream.[x.Position..(x.Position + size - 1)]) 

    member x.Read (converter : byte[]*int -> 'T) : StreamPosition*StreamReaderResult<'T> = 
    let size = sizeof<'T> 
    if x.Remaining < size then 
     x, Failure ["EOS", x] 
    else 
     let nsp = StreamPosition.New x.Stream (x.Position + size) 
     nsp, Success (converter (x.Stream, x.Position)) 

    static member New s p = {Stream = s; Position = p;} 

// Defining the StreamReader<'T> function is the most important decision 
// In this case a stream reader is a function that takes a StreamPosition 
// and produces a (potentially) new StreamPosition and a StreamReadeResult 
type StreamReader<'T> = StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>) 

// Defining the StreamReader CE 
module StreamReader = 
    let Return v : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     sp, (Success v) 

    let Bind (StreamReader t) (fu : 'T -> StreamReader<'U>) : StreamReader<'U> = 
    StreamReader <| fun sp -> 
     let tsp, tr = t sp 
     match tr with 
     | Success tv -> 
     let (StreamReader u) = fu tv 
     u tsp 
     | Failure tfs -> tsp, Failure tfs 

    let Yield (ft : unit -> StreamReader<'T>) : StreamReader<'T> = 
    StreamReader <| fun sp -> 
     let (StreamReader t) = ft() 
     t sp 

type StreamReaderBuilder() = 
    member x.Return v = StreamReader.Return v 
    member x.Bind(t,fu) = StreamReader.Bind t fu 
    member x.Yield(ft) = StreamReader.Yield ft 

let reader = StreamReaderBuilder() 

let read (StreamReader sr) (bytes : byte[]) (pos : int) : StreamReaderResult<'T> = 
    let sp = StreamPosition.New bytes pos 
    let _, sr = sr sp 
    sr 

// Defining various stream reader functions 
let readValue (converter : byte[]*int -> 'T) : StreamReader<'T> = 
    StreamReader <| fun sp -> sp.Read converter 

let readInt32 = readValue BitConverter.ToInt32 
let readInt16 = readValue BitConverter.ToInt16 
let readBytes size : StreamReader<byte[]> = 
    StreamReader <| fun sp -> 
    sp.ReadBytes size 

let readImage = 
    reader { 
    let! width = readInt32 
    let! height = readInt32 
    let! bytes = readBytes (width*height) 

    return width, height, bytes 
    } 

[<EntryPoint>] 
let main argv = 
    // Sample byte stream 
    let bytes = [|2;0;0;0;3;0;0;0;1;2;3;4;5;6|] |> Array.map byte 
    let result = read readImage bytes 0 

    printfn "%A" result 

    0 
+0

Teşekkürler, sanırım ana fikri anladım - monadik türün kendisi bir değer değil, bir fonksiyon durumu -> durum * sonucu, bu yüzden Bind (ve tüm ifade de) zincirden aşağı akan geçiş konumunu sağlayan bu işlevi döndürür. Ve benim yazımda "For" döngülerinin ve mutabilitesinin yanlış olduğunu görüyorum, şimdi doğru şekilde yazmaya çalışacağım. – Dzugaru

+0

Evet, bu doğru. Ayrıştırıcı işlevlerini oluşturmak çok güçlüdür. Örneğin 'FParsec' (ayrıştırıcı kütüphanesi) 'e bakarsanız, benzer bir yaklaşım kullandığını görürsünüz. – FuleSnabel

+0

Biraz daha fazla açıklama eklendi. – FuleSnabel