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

Popular posts from this blog

facebook - android ACTION_SEND to share with specific application only -

python - Creating a new virtualenv gives a permissions error -

javascript - cocos2d-js draw circle not instantly -