@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
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
Çö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