F# computation expression transparent state passing with Bind -
i have following code try read possibly incomplete data (image data, example) network stream using usual maybebuilder:
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} }
so, readstream[asint] [numbytes] [offset] function returns [data] or none if data has not arrived yet in networkstream. advanceinstream function executed when whole network packet read.
i wonder if there way write custom computation expression builder hide pos passing user, since it's same - read data , position in stream , pass next read function last parameter.
p.s. maybebuilder used:
type maybebuilder() = member x.bind(d,f) = option.bind f d member x.return d = d member x.returnfrom d = d member x.zero() = none let maybe = new maybebuilder()
p.p.s
on second thought seems have make pos mutable, because of possible "for" or "while" loops in reading. simple let! works fine pos bind shadowing, can't hold onto immutability if add reading in loop, right? task becomes trivial then.
@bytebuster making points of maintainability custom computation expressions still thought demonstrate how combine state
, maybe
monad one.
in "traditional" languages have support composing values such integers run problems when developing parsers (producing values binary stream parsing). parsers compose simple parser functions more complex parser functions here "traditional" languages lack support.
in functional languages functions ordinary values , since values can composed functions can well.
first let's define streamreader
function. streamreader
takes streamposition
(stream + position) , produces updated streamposition
, streamreaderresult
(the read value or failure).
type streamreader<'t> = streamreader of (streamposition -> streamposition*streamreaderresult<'t>)
(this important step.)
we able compose simple streamreader
functions more complex ones. important property want maintain compose operation "closed" under streamreader
meaning result of composition new streamreader
in turn can composed endlessly.
in order read image need read width & height, compute product , read bytes. this:
let readimage = reader { let! width = readint32 let! height = readint32 let! bytes = readbytes (width*height) return width, height, bytes }
because of composition being closed readimage
streamreader<int*int*byte[]>
.
in order able compose streamreader
above need define computation expression before can need define operation return
, bind
streamreader
. turns out yield
have well.
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 | 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
trivial streamreader
should return given value , don't update streamposition
.
bind
bit more challenging describes how compose 2 streamreader
functions new one. bind
runs first streamreader
function , checks result, if it's failure returns failure otherwise uses streamreader
result compute second streamreader
, runs on update stream position.
yield
creates streamreader
function , runs it. yield
used f# when building computation expressions.
finally let's create computation expression builder
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 ()
now built basic framework combining streamreader
functions. in addition need define primitive streamreader
functions.
full example:
open system open system.io // result of stream reader operation either // success of value // failure of list of failures type streamreaderresult<'t> = | success of 't | failure of (string*streamposition) list , 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 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 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 streamreader<'t> function important decision // in case stream reader function takes streamposition // , produces (potentially) new streamposition , streamreaderesult type streamreader<'t> = streamreader of (streamposition -> streamposition*streamreaderresult<'t>) // defining 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 | 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
Comments
Post a Comment