3 people like it.

Disposable Scopes provided by scope {...} computational workflow.

This snippet provides a builder for scope {...} computational workflow that returns a Scope object implementing System.IDisposable interface. All "use" bindings inside scope {...} workflow are kept from being disposed immediately when computation leaves scope. Instead, they are disposed only when resulting Scope object is disposed. This makes possible to access disposable outside the scope while it remains usable. This can be achieved by via return statement of workflow that can return, for example, a closure that encapsulates disposable. scope {...} return value is available through Value property of Scope object. Scopes may be nested through let! binding that means that nested scope will be disposed when the resulting scope is disposed.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
144: 
145: 
146: 
147: 
148: 
149: 
150: 
151: 
152: 
153: 
154: 
155: 
156: 
157: 
158: 
159: 
160: 
161: 
162: 
163: 
164: 
165: 
166: 
167: 
168: 
169: 
170: 
171: 
172: 
173: 
174: 
175: 
176: 
177: 
178: 
179: 
180: 
181: 
182: 
183: 
184: 
185: 
186: 
187: 
188: 
189: 
190: 
191: 
192: 
193: 
194: 
195: 
196: 
197: 
198: 
199: 
200: 
201: 
202: 
203: 
204: 
205: 
206: 
207: 
208: 
209: 
210: 
211: 
212: 
213: 
214: 
215: 
216: 
217: 
218: 
219: 
220: 
221: 
222: 
223: 
224: 
225: 
226: 
227: 
228: 
229: 
230: 
231: 
232: 
233: 
234: 
235: 
236: 
237: 
238: 
239: 
240: 
241: 
242: 
243: 
244: 
245: 
246: 
247: 
248: 
249: 
250: 
251: 
252: 
253: 
254: 
255: 
256: 
257: 
258: 
259: 
260: 
261: 
open System
type Interlocked = System.Threading.Interlocked

type Scope<'a> (value:'a) =
    let queue = Collections.Generic.Queue<IDisposable>()
    let mutable closed = 0
    let close () = 
        let mutable exnList = [] // where we collect errors while inner objects are being disposed
        for i in queue do
            try
                i.Dispose()
            with 
                exn ->  exnList <- exn :: exnList 
        match exnList with
        | [] -> ()  // no errors occurred
        | [exn] -> raise exn
        | _ -> raise (AggregateException exnList)  
    member _.Value = value
    member internal _.InnerDisposables = queue
    member _.Close () =        
        match Interlocked.CompareExchange(&closed,1,0) with
        | 0 -> close () // will run at most once
        | _ -> ()

 

    interface IDisposable with
        member this.Dispose () = this.Close()

module UnsafeInternals =
    let getInnerDisposables (x:Scope<_>) = x.InnerDisposables
    (*
        may be useful to provide builder extensions.
        Note that there is unique instance of Scope inside workflow that is created by 
        Return function. It is not directly accessible from inside workflow. Other builder functions maintain its mutable state.
        And they all run eagerly in one thread right now. However, builder extension method may access that instance. Thus, if anyone wants to pass
        Scope instance between threads he must make a byvalue copy of it.
    *)

type ScopeBuilder () =
    member _. Return x = new Scope<_>(x)
        
    member _.Using (res ,body) =
        try
            let (x:Scope<_>) = body res
            x.InnerDisposables.Enqueue res
            x
        with
            | exn -> 
                try
                    res.Dispose()
                with
                    | exn1 ->
                        raise (AggregateException [|exn ; exn1|])
                reraise()           

    
    member this.Bind (scope, body) =
        let outerbody (scope' : Scope<_>) = body scope'.Value
        this.Using (scope,outerbody)
        (*
            thus, let! x = {expr}
             -- is equivalent to --
            use scope = {expr}
                    // outerbody starts here
            let x = scope.Value
                    // body starts here
        *)
    member this.ReturnFrom (anotherscope:Scope<_>) = 
        this.Using(anotherscope,fun thatscope -> this.Return thatscope.Value)
        (*
            created current scope using value of another scope 
            bound another scope to current scope as disposable
        *)


       
[<AutoOpen>]
module ScopeBuilderInstance =
    let scope = ScopeBuilder () 

// example
open System.IO
let somescope =
    scope {
        let bytes = System.Text.UTF8Encoding().GetBytes "this
is
multiline
text"
        use ms = new MemoryStream(bytes) 
        use sr = new StreamReader(ms)
        return 
            {|
                Next = sr.ReadLine
                MemoryStream =  ms
            |}

    }

somescope.Value.Next()  // reads text line by line

somescope.Value.MemoryStream // view state of memory stream in fsharp interactive

somescope.Close() // try execute above commands now

// demonstrate what happens on error and also disposing order
// define 2 helper functions
let useful name =
    {
        new IDisposable with
            member _.Dispose () = printfn "disposed %s" name            
    }
let useless name =
    {
        new IDisposable with
            member _.Dispose () = failwithf "failed to dispose %s" name
    }

// demo : order  of disposing
scope {
    use _ = useful "1"
    use _ = useful "2"
    use _ = useful "3"
    return 4
}  |> (fun s -> s.Close() ; s.Close((*closing twice takes no effect*)))

// composite scope
scope {
    use _ = useful "1"
    use _ = useful "2"
    let! v = 
        scope {
            use _ = useful "4"
            use _ = useful "5"
            return 10
        }
    use _ = useful "3"
    return v + 5
}  |> (fun s -> printfn "value is %i" s.Value ; s.Close())


let sc1 =
        scope {
            use _ = useful "4"
            use _ = useful "5"
            return 10
        }
//sc1.Close()  // try to close sc1 before executing following statements      
scope {
    use _ = useful "1"
    use _ = useful "2"
    return! sc1
}  |> (fun s -> printfn "value is %i" s.Value ; s.Close())


let throwingscope =
    scope {
    use _ = useful "1"
    use _ = useful "2"
    use _ = useless "3"
    use _ = useful "4"
    use _ = useless "5"
    return ()
    }

// now we shall close it

try
   throwingscope.Close() 
with
| :? AggregateException as agexn ->
    printfn "AggregateException\n%s" <| agexn.Flatten().Message
| exn ->
    printfn "just an exception\n %s" exn.Message  

// try execute the same code outside computation expression 

try
    use _ = useful "1"
    use _ = useful "2"
    use _ = useless "3"
    use _ = useful "4"
    use _ = useless "5" // exception is lost 
    ()
with
| :? AggregateException as agexn ->
    printfn "AggregateException\n%s" <| agexn.Flatten().Message
| exn ->
    printfn "just an exception\n %s" exn.Message 

// when exception occurs when creating scope  

try    
    scope {
    use _ = useful "1"
    use _ = useful "2"
    use _ = useless "3"
    failwith "error!!!"
    use _ = useful "4"
    return ()
    } |> ignore
with
| :? AggregateException as agexn ->
    printfn "AggregateException\n%s" <| agexn.Flatten().Message
| exn ->
    printfn "just an exception\n %s" exn.Message  

(*---------some tricks we can use with scopes--------*)

// preparation 1 : helper type
// we'll use it to construct guarded methods
type Disposable (?name) =
    let objectName = defaultArg  name "disposable"
    let mutable isDisposed = false
    member _.Close() = isDisposed <- true
    member _.IsDisposed = isDisposed
    member _.Check() =
        if isDisposed then 
            raise (ObjectDisposedException objectName)
    interface IDisposable with
        member this.Dispose () = this.Close()

// preparation 2 : helper function
// use IDisposable as pattern to autoclean filesystem of temporary files 
module File =
    // file at path will be deleted on dispose
    let autoDelete path =
            {
                new IDisposable with
                member _.Dispose() =
                    if File.Exists path then                        
                        File.Delete path
            }              
// example
let notebookScope =
    scope {
        use _ = File.autoDelete "tempfile" // tempfile willbe deleted on dispose
        use guard = new Disposable "notebook"
        return 
            {|
                WriteLine = 
                    fun line ->
                    guard.Check()
                    File.AppendAllLines ("tempfile",[|line|])
                Print =
                    fun () ->
                    guard.Check()
                    File.ReadAllText "tempfile"
                    |> printfn "%s"

            |}
    }

let notebook = notebookScope.Value

notebook.WriteLine "Hello World!" // tempfile appears in your IDE explorer
notebook.WriteLine "Our experience with scopes"
notebook.Print()

notebookScope.Close() // tempfile disappeared
notebook.Print() // ObjectDisposedException thrown by guard
namespace System
type Interlocked = Threading.Interlocked
namespace System.Threading
type Interlocked =
  static member Add : location1:int * value:int -> int + 1 overload
  static member CompareExchange : location1:int * value:int * comparand:int -> int + 6 overloads
  static member Decrement : location:int -> int + 1 overload
  static member Exchange : location1:int * value:int -> int + 6 overloads
  static member Increment : location:int -> int + 1 overload
  static member MemoryBarrier : unit -> unit
  static member MemoryBarrierProcessWide : unit -> unit
  static member Read : location:int64 -> int64
Multiple items
type Scope<'a> =
  interface IDisposable
  new : value:'a -> Scope<'a>
  member Close : unit -> unit
  member internal InnerDisposables : Queue<IDisposable>
  member Value : 'a

--------------------
new : value:'a -> Scope<'a>
val value : 'a
val queue : Collections.Generic.Queue<IDisposable>
Multiple items
namespace System.Collections

--------------------
namespace Microsoft.FSharp.Collections
namespace System.Collections.Generic
Multiple items
type Queue<'T> =
  new : unit -> Queue<'T> + 2 overloads
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member CopyTo : array:'T[] * arrayIndex:int -> unit
  member Count : int
  member Dequeue : unit -> 'T
  member Enqueue : item:'T -> unit
  member GetEnumerator : unit -> Enumerator<'T>
  member Peek : unit -> 'T
  member ToArray : unit -> 'T[]
  ...
  nested type Enumerator

--------------------
Collections.Generic.Queue() : Collections.Generic.Queue<'T>
Collections.Generic.Queue(capacity: int) : Collections.Generic.Queue<'T>
Collections.Generic.Queue(collection: Collections.Generic.IEnumerable<'T>) : Collections.Generic.Queue<'T>
type IDisposable =
  member Dispose : unit -> unit
val mutable closed : int
val close : (unit -> unit)
val mutable exnList : exn list
val i : IDisposable
Multiple items
val exn : exn

--------------------
type exn = Exception
val raise : exn:Exception -> 'T
Multiple items
type AggregateException =
  inherit Exception
  new : unit -> AggregateException + 6 overloads
  member Flatten : unit -> AggregateException
  member GetBaseException : unit -> Exception
  member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
  member Handle : predicate:Func<Exception, bool> -> unit
  member InnerExceptions : ReadOnlyCollection<Exception>
  member Message : string
  member ToString : unit -> string

--------------------
AggregateException() : AggregateException
AggregateException(message: string) : AggregateException
AggregateException(innerExceptions: Collections.Generic.IEnumerable<exn>) : AggregateException
AggregateException([<ParamArray>] innerExceptions: exn []) : AggregateException
AggregateException(message: string, innerException: exn) : AggregateException
AggregateException(message: string, innerExceptions: Collections.Generic.IEnumerable<exn>) : AggregateException
AggregateException(message: string, [<ParamArray>] innerExceptions: exn []) : AggregateException
Threading.Interlocked.CompareExchange<'T (requires reference type)>(location1: byref<'T>, value: 'T, comparand: 'T) : 'T
Threading.Interlocked.CompareExchange(location1: byref<nativeint>, value: nativeint, comparand: nativeint) : nativeint
Threading.Interlocked.CompareExchange(location1: byref<obj>, value: obj, comparand: obj) : obj
Threading.Interlocked.CompareExchange(location1: byref<float>, value: float, comparand: float) : float
Threading.Interlocked.CompareExchange(location1: byref<float32>, value: float32, comparand: float32) : float32
Threading.Interlocked.CompareExchange(location1: byref<int64>, value: int64, comparand: int64) : int64
Threading.Interlocked.CompareExchange(location1: byref<int>, value: int, comparand: int) : int
val this : Scope<'a>
val getInnerDisposables : x:Scope<'a> -> Collections.Generic.Queue<IDisposable>
val x : Scope<'a>
Multiple items
type ScopeBuilder =
  new : unit -> ScopeBuilder
  member Bind : scope:Scope<'b> * body:('b -> Scope<'c>) -> Scope<'c>
  member Return : x:'f -> Scope<'f>
  member ReturnFrom : anotherscope:Scope<'a> -> Scope<'a>
  member Using : res:'d * body:('d -> Scope<'e>) -> Scope<'e> (requires 'd :> IDisposable)

--------------------
new : unit -> ScopeBuilder
val x : 'f
val res : #IDisposable
val body : (#IDisposable -> Scope<'e>)
val x : Scope<'e>
val exn1 : exn
val reraise : unit -> 'T
val this : ScopeBuilder
val scope : Scope<'b>
val body : ('b -> Scope<'c>)
val outerbody : (Scope<'b> -> Scope<'c>)
val scope' : Scope<'b>
val anotherscope : Scope<'a>
val thatscope : Scope<'a>
Multiple items
type AutoOpenAttribute =
  inherit Attribute
  new : unit -> AutoOpenAttribute
  new : path:string -> AutoOpenAttribute
  member Path : string

--------------------
new : unit -> AutoOpenAttribute
new : path:string -> AutoOpenAttribute
val scope : ScopeBuilder
namespace System.IO
val somescope : Scope<{| MemoryStream: MemoryStream; Next: (unit -> string) |}>
val bytes : byte []
namespace System.Text
Multiple items
type UTF8Encoding =
  inherit Encoding
  new : unit -> UTF8Encoding + 2 overloads
  member Equals : value:obj -> bool
  member GetByteCount : chars:string -> int + 3 overloads
  member GetBytes : chars:ReadOnlySpan<char> * bytes:Span<byte> -> int + 3 overloads
  member GetCharCount : bytes:ReadOnlySpan<byte> -> int + 2 overloads
  member GetChars : bytes:ReadOnlySpan<byte> * chars:Span<char> -> int + 2 overloads
  member GetDecoder : unit -> Decoder
  member GetEncoder : unit -> Encoder
  member GetHashCode : unit -> int
  member GetMaxByteCount : charCount:int -> int
  ...

--------------------
Text.UTF8Encoding() : Text.UTF8Encoding
Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: bool) : Text.UTF8Encoding
Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: bool, throwOnInvalidBytes: bool) : Text.UTF8Encoding
val ms : MemoryStream
Multiple items
type MemoryStream =
  inherit Stream
  new : unit -> MemoryStream + 6 overloads
  member CanRead : bool
  member CanSeek : bool
  member CanWrite : bool
  member Capacity : int with get, set
  member CopyTo : destination:Stream * bufferSize:int -> unit
  member CopyToAsync : destination:Stream * bufferSize:int * cancellationToken:CancellationToken -> Task
  member Flush : unit -> unit
  member FlushAsync : cancellationToken:CancellationToken -> Task
  member GetBuffer : unit -> byte[]
  ...

--------------------
MemoryStream() : MemoryStream
MemoryStream(capacity: int) : MemoryStream
MemoryStream(buffer: byte []) : MemoryStream
MemoryStream(buffer: byte [], writable: bool) : MemoryStream
MemoryStream(buffer: byte [], index: int, count: int) : MemoryStream
MemoryStream(buffer: byte [], index: int, count: int, writable: bool) : MemoryStream
MemoryStream(buffer: byte [], index: int, count: int, writable: bool, publiclyVisible: bool) : MemoryStream
val sr : StreamReader
Multiple items
type StreamReader =
  inherit TextReader
  new : stream:Stream -> StreamReader + 10 overloads
  member BaseStream : Stream
  member Close : unit -> unit
  member CurrentEncoding : Encoding
  member DiscardBufferedData : unit -> unit
  member EndOfStream : bool
  member Peek : unit -> int
  member Read : unit -> int + 2 overloads
  member ReadAsync : buffer:Memory<char> * ?cancellationToken:CancellationToken -> ValueTask<int> + 1 overload
  member ReadBlock : buffer:Span<char> -> int + 1 overload
  ...

--------------------
StreamReader(stream: Stream) : StreamReader
   (+0 other overloads)
StreamReader(path: string) : StreamReader
   (+0 other overloads)
StreamReader(stream: Stream, detectEncodingFromByteOrderMarks: bool) : StreamReader
   (+0 other overloads)
StreamReader(stream: Stream, encoding: Text.Encoding) : StreamReader
   (+0 other overloads)
StreamReader(path: string, detectEncodingFromByteOrderMarks: bool) : StreamReader
   (+0 other overloads)
StreamReader(path: string, encoding: Text.Encoding) : StreamReader
   (+0 other overloads)
StreamReader(stream: Stream, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool) : StreamReader
   (+0 other overloads)
StreamReader(path: string, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool) : StreamReader
   (+0 other overloads)
StreamReader(stream: Stream, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool, bufferSize: int) : StreamReader
   (+0 other overloads)
StreamReader(path: string, encoding: Text.Encoding, detectEncodingFromByteOrderMarks: bool, bufferSize: int) : StreamReader
   (+0 other overloads)
val useful : name:string -> IDisposable
val name : string
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
val useless : name:string -> IDisposable
val failwithf : format:Printf.StringFormat<'T,'Result> -> 'T
val s : Scope<int>
val v : int
val sc1 : Scope<int>
val throwingscope : Scope<unit>
val agexn : AggregateException
val failwith : message:string -> 'T
val ignore : value:'T -> unit
Multiple items
type Disposable =
  interface IDisposable
  new : ?name:string -> Disposable
  member Check : unit -> unit
  member Close : unit -> unit
  member IsDisposed : bool

--------------------
new : ?name:string -> Disposable
val name : string option
val objectName : string
val defaultArg : arg:'T option -> defaultValue:'T -> 'T
val mutable isDisposed : bool
Multiple items
type ObjectDisposedException =
  inherit InvalidOperationException
  new : objectName:string -> ObjectDisposedException + 2 overloads
  member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
  member Message : string
  member ObjectName : string

--------------------
ObjectDisposedException(objectName: string) : ObjectDisposedException
ObjectDisposedException(objectName: string, message: string) : ObjectDisposedException
ObjectDisposedException(message: string, innerException: exn) : ObjectDisposedException
val this : Disposable
type File =
  static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
  static member AppendAllLinesAsync : path:string * contents:IEnumerable<string> * ?cancellationToken:CancellationToken -> Task + 1 overload
  static member AppendAllText : path:string * contents:string -> unit + 1 overload
  static member AppendAllTextAsync : path:string * contents:string * ?cancellationToken:CancellationToken -> Task + 1 overload
  static member AppendText : path:string -> StreamWriter
  static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
  static member Create : path:string -> FileStream + 2 overloads
  static member CreateText : path:string -> StreamWriter
  static member Decrypt : path:string -> unit
  static member Delete : path:string -> unit
  ...
val autoDelete : path:string -> IDisposable
val path : string
File.Exists(path: string) : bool
File.Delete(path: string) : unit
val notebookScope : Scope<{| Print: (unit -> unit); WriteLine: (string -> unit) |}>
val guard : Disposable
val line : string
File.AppendAllLines(path: string, contents: Collections.Generic.IEnumerable<string>) : unit
File.AppendAllLines(path: string, contents: Collections.Generic.IEnumerable<string>, encoding: Text.Encoding) : unit
File.ReadAllText(path: string) : string
File.ReadAllText(path: string, encoding: Text.Encoding) : string
val notebook : {| Print: (unit -> unit); WriteLine: (string -> unit) |}

More information

Link:http://fssnip.net/87q
Posted:1 year ago
Author:Crazy Monk
Tags: computational workflows , computation builder , monad