The persisted multistep resumable monad
This module implements the persisted & multistep implementation of the
resumable monad.
Multistep means that the where the resumable expression is encoded as
a mapping from the trace history type 'h to the expression's return type 't
as opposed to a single step semantics ('h -> 'h * Option<'t>
).
Persisted means that the trace of caching points is persisted to some external storage device
as the computation progresses. If the computation is interrupted for any external reason (e.g., machine shuts down, process killed, machine upgraded, ...)
then the computation can be resumed from the persisted stated and resume from the last evaluated caching point.
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:
|
/// Persisted implementation of the multistep resumable monad.
module ResumableMonad.MultipstepPersisted
#if INTERACTIVE
#load "zero.fs"
#load "Scripts\load-references-debug.fsx"
#endif
/// Defines a method to persist a trace history (i.e. caching points) to some external device
type Persist<'h> = 'h -> unit
/// Defines the functions to load/save the caching points to some external storage
type CacheStorage<'h> =
{
save : Persist<'h>
load : unit -> 'h option
}
/// A resumable computation of type `'t` with caching points history of type `'h`.
/// - `'t` is the returned type of the computation
/// - type `'h` is inferred from the monadic expression and encodes the history of caching points in the
/// resumable expression. The hierachy of caching points is encoded with nested tuples, the leaf elements in the
/// hiearachy are of type `'a option` and represent the caching points themselves.
type Resumable<'h,'t> = Resumable of (Persist<'h> -> 'h -> 't)
with
/// Resume from a specified history of caching points
member inline R.resume (persist:Persist<'h>) (h:'h) =
let (Resumable r) = R
r persist h
/// Returns the empty history (i.e. no caching point)
member inline __.initial =
Zero.getZeroTyped<'h>
/// Evaluate the resumable expression starting
/// from the saved history of cached points if it exists,
/// or from the empty history otherwise
member inline R.evaluate (storage:CacheStorage<'h>) =
let (Resumable resume) = R
printfn "State type is %O" typeof<'h>
let state =
match storage.load() with
| None ->
printfn "No cached state in storage: starting from initial state"
R.initial
| Some state ->
printfn "Resuming from existing cached state"
state
resume storage.save state
/// A resumable computation of type `'t` with no caching point.
/// This extra type is used as a trick to match
/// on type `'h` at compile-type using .net member overloading
/// (since unfortunatley the static constraint `not ('h :> unit)` cannot be expressed in F#).
//
/// It's not theoretically needed but it helps simplify
/// the type encoding `'h` of caching points by eliminating
/// redundant occurrences of `option unit` within
/// larger resumable expressions.
and Resumable<'t> = Startable of (unit -> 't)
with
member inline R.resume () =
let (Startable r) = R
r ()
/// Return the provided value if specified otherwise evaluate the provided function
/// and persist the result to the external storage device
let inline getOrEvaluate (persist:Persist<'a>) (evaluate: unit -> 'a) = function
| Some cached ->
printfn "Reusing cached value: %O" cached
cached
| None ->
printfn "Cache miss: evaluating resumable expression..."
let r = evaluate()
printfn "Persisting result to cache: %O" r
persist r
r
/// Return a persist function that applies a transformation and then falls back to the specified persist function
let inline (+~) (persist:Persist<'h>) (cons:'a->'h) :Persist<'a> = cons >> persist
/// The syntax builder for the Resumable monadic syntax
type ResumableBuilder() =
member __.Zero<'t>() : Resumable<_> =
Startable <| fun () -> ()
member __.Return(x:'t) =
Startable <| fun () -> x
member __.Delay(f: unit -> Resumable<'h,'t>) =
printfn "===Delay"
Resumable <| (fun (p:Persist<'h>) h -> f().resume p h)
member __.Delay(f: unit -> Resumable<'t>) =
Startable <| fun () -> f().resume ()
// Resumable<'u,'a> -> ('a->Resumable<'v, 'b>) -> Resumable<'a option * 'u * 'v, 'b>
member inline __.Bind(f:Resumable<'u,'a>, g:'a->Resumable<'v, 'b>) =
printfn "===1: 'u:%O 'a: %O 'v: %O 'b: %O" typeof<'u> typeof<'a> typeof<'v> typeof<'b>
Resumable (fun (p:Persist<'a option * 'u * 'v>) (cached:'a option, u:'u, v:'v) ->
let persista = p +~ fun a -> Some a, u, v
let persistu = p +~ fun u -> None, u, v
let a = cached |> getOrEvaluate persista (fun () -> f.resume persistu u)
(g a).resume (p +~ fun v -> Some a, u, v) v)
// Resumable<'u,'a> -> ('a->Resumable<'b>) -> Resumable<'a option * 'u, 'b>
member inline __.Bind(f:Resumable<'u,'a>, g:'a->Resumable<'b>) =
printfn "===2: 'u: %O 'a: %O 'b:%O" typeof<'u> typeof<'a> typeof<'b>
Resumable (fun (p:Persist<'a option * 'u>) (cached, u) ->
let persista = p +~ fun a -> Some a, u
let persistu = p +~ fun u -> None, u
(cached |> getOrEvaluate persista (fun () -> f.resume persistu u) |> g).resume())
// Resumable<'a> -> ('a->Resumable<'v, 'b>) -> Resumable<'a option * 'v, 'b> =
member inline __.Bind(f:Resumable<'a>, g:'a->Resumable<'v, 'b>) =
printfn "===3: 'a: %O 'v: %O 'b:%O" typeof<'a> typeof<'v> typeof<'b>
Resumable (fun (p:Persist<'a option * 'v>) (cached:'a option, v) ->
let a :'a = cached |> getOrEvaluate (p +~ fun a -> Some a, v) f.resume
(g a).resume (p +~ fun v -> Some a, v) v)
// Resumable<'a> -> ('a->Resumable<'b>) -> Resumable<'a option, 'b> =
member inline __.Bind(f:Resumable<'a>, g:'a->Resumable<'b>) =
printfn "===4: 'a: %O 'b: %O" typeof<'a> typeof<'b>
Resumable (fun (p:Persist<'a option>) cached ->
let a = cached |> getOrEvaluate (p +~ Some) f.resume
(g a).resume())
// Resumable<'a> -> ('a->Resumable<'b>) -> Resumable<'b>
member inline __.BindNoCache(f:Resumable<'a>, g:'a->Resumable<'b>) =
printfn "===5: 'a: %O 'b: %O" typeof<'a> typeof<'b>
Startable (fun () -> (g <| f.resume()).resume())
// Resumable<'u,unit> -> Resumable<'v,'b> -> Resumable<'u * 'v,'b>
member inline __.Combine(p1:Resumable<'u,unit>, p2:Resumable<'v,'b>) =
printfn "===6"
Resumable (fun (p:Persist<'u * 'v>) (u, v) ->
p1.resume (p +~ fun u -> u, v) u
p2.resume (p +~ fun v -> u, v) v)
// Resumable<unit> -> Resumable<'b> -> Resumable<'b>
member inline __.Combine(p1:Resumable<unit>, p2:Resumable<'b>) =
printfn "===7"
Startable (fun () -> p1.resume(); p2.resume())
member __.While(condition, body:Resumable<unit>) : Resumable<unit> =
if condition() then
__.BindNoCache(body, (fun () -> __.While(condition, body)))
else
__.Zero()
|
We now define the computational expression resumable { ... }
with all
the syntactic sugar automatically inferred from the above monadic operators.
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:
|
let resumable = ResumableBuilder()
/// File storage based on Newtonsoft serialization
let inline NewtonsoftStorage< ^T> fileName =
{
save = fun history -> System.IO.File.WriteAllText(fileName, Newtonsoft.Json.JsonConvert.SerializeObject(history))
load = fun () ->
if System.IO.File.Exists fileName then
Some <| Newtonsoft.Json.JsonConvert.DeserializeObject< ^T>(System.IO.File.ReadAllText(fileName))
else
None
}
/// File storage based on FSharpLu.Json
let inline LuStorage< ^T> fileName =
{
save = Microsoft.FSharpLu.Json.Compact.serializeToFile fileName
load = fun () ->
if System.IO.File.Exists fileName then
printfn "Loading cached points from file %s" fileName
let cache = Microsoft.FSharpLu.Json.Compact.tryDeserializeFile< ^T> fileName
match cache with
| Choice1Of2 cache -> Some cache
| Choice2Of2 _ -> None
else
printfn "Cache history file not found, starting computation from scratch."
None
}
|
A simple example: finding three large prime numbers and summing them up
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:
|
module Example =
open System
let isPrime = function
| 1 -> false
| 2 -> true
| n -> { 2..(int <| Math.Ceiling(Math.Sqrt(float n))) } |> Seq.forall (fun d -> n % d <> 0)
let nthprime n =
printfn "Calculating %dth prime..." n
let p = Seq.initInfinite id |> Seq.where isPrime |> Seq.item n
printfn "%dth prime is: %d" n p
p
let addPrimes n1 n2 n3 =
resumable {
printfn "Starting computation"
let! p1 = resumable { return nthprime n1 }
printfn "Press enter to continue or CTRL+BREAK to pause the calculation here."
System.Console.Read() |> ignore
let! p2 = resumable { return nthprime n2 }
printfn "Press enter to continue or CTRL+BREAK to pause the calculation here."
System.Console.Read()|> ignore
let! p3 = resumable { return nthprime n3 }
let sum = p1 + p2 + p3
printfn "The sum of %dth prime, %dth prime and %dth prime is %d" n1 n2 n3 sum
return sum
}
let f = System.IO.Path.GetTempFileName()
printfn "State file is %s" f
System.IO.File.Delete(f)
let result = (addPrimes 30000 20000 10000).evaluate (LuStorage f)
printfn "Result of the resumable computation is %d" result
printf "%s" <| System.IO.File.ReadAllText(f)
|
namespace ResumableMonad
module MultipstepPersisted
from ResumableMonad
Persisted implementation of the multistep resumable monad.
type Persist<'h> = 'h -> unit
Full name: ResumableMonad.MultipstepPersisted.Persist<_>
Defines a method to persist a trace history (i.e. caching points) to some external device
type unit = Unit
Full name: Microsoft.FSharp.Core.unit
type CacheStorage<'h> =
{save: Persist<'h>;
load: unit -> 'h option;}
Full name: ResumableMonad.MultipstepPersisted.CacheStorage<_>
Defines the functions to load/save the caching points to some external storage
CacheStorage.save: Persist<'h>
CacheStorage.load: unit -> 'h option
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
Multiple items
union case Resumable.Resumable: (Persist<'h> -> 'h -> 't) -> Resumable<'h,'t>
--------------------
type Resumable<'t> =
| Startable of (unit -> 't)
member resume : unit -> 't
Full name: ResumableMonad.MultipstepPersisted.Resumable<_>
A resumable computation of type `'t` with no caching point.
This extra type is used as a trick to match
on type `'h` at compile-type using .net member overloading
(since unfortunatley the static constraint `not ('h :> unit)` cannot be expressed in F#).
It's not theoretically needed but it helps simplify
the type encoding `'h` of caching points by eliminating
redundant occurrences of `option unit` within
larger resumable expressions.
--------------------
type Resumable<'h,'t> =
| Resumable of (Persist<'h> -> 'h -> 't)
member evaluate : storage:CacheStorage<'h> -> 't
member initial : 'h
member resume : persist:Persist<'h> -> h:'h -> 't
Full name: ResumableMonad.MultipstepPersisted.Resumable<_,_>
A resumable computation of type `'t` with caching points history of type `'h`.
- `'t` is the returned type of the computation
- type `'h` is inferred from the monadic expression and encodes the history of caching points in the
resumable expression. The hierachy of caching points is encoded with nested tuples, the leaf elements in the
hiearachy are of type `'a option` and represent the caching points themselves.
val R : Resumable<'h,'t>
member Resumable.resume : persist:Persist<'h> -> h:'h -> 't
Full name: ResumableMonad.MultipstepPersisted.Resumable`2.resume
Resume from a specified history of caching points
val persist : Persist<'h>
val h : 'h
val r : (Persist<'h> -> 'h -> 't)
member Resumable.initial : 'h
Full name: ResumableMonad.MultipstepPersisted.Resumable`2.initial
Returns the empty history (i.e. no caching point)
member Resumable.evaluate : storage:CacheStorage<'h> -> 't
Full name: ResumableMonad.MultipstepPersisted.Resumable`2.evaluate
Evaluate the resumable expression starting
from the saved history of cached points if it exists,
or from the empty history otherwise
val storage : CacheStorage<'h>
val resume : (Persist<'h> -> 'h -> 't)
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val typeof<'T> : System.Type
Full name: Microsoft.FSharp.Core.Operators.typeof
val state : 'h
union case Option.None: Option<'T>
property Resumable.initial: 'h
Returns the empty history (i.e. no caching point)
union case Option.Some: Value: 'T -> Option<'T>
union case Resumable.Startable: (unit -> 't) -> Resumable<'t>
val R : Resumable<'t>
member Resumable.resume : unit -> 't
Full name: ResumableMonad.MultipstepPersisted.Resumable`1.resume
val r : (unit -> 't)
val getOrEvaluate : persist:Persist<'a> -> evaluate:(unit -> 'a) -> _arg1:'a option -> 'a
Full name: ResumableMonad.MultipstepPersisted.getOrEvaluate
Return the provided value if specified otherwise evaluate the provided function
and persist the result to the external storage device
val persist : Persist<'a>
val evaluate : (unit -> 'a)
val cached : 'a
val r : 'a
val cons : ('a -> 'h)
Multiple items
type ResumableBuilder =
new : unit -> ResumableBuilder
member Bind : f:Resumable<'u,'a> * g:('a -> Resumable<'v,'b>) -> Resumable<('a option * 'u * 'v),'b>
member Bind : f:Resumable<'u,'a> * g:('a -> Resumable<'b>) -> Resumable<('a option * 'u),'b>
member Bind : f:Resumable<'a> * g:('a -> Resumable<'v,'b>) -> Resumable<('a option * 'v),'b>
member Bind : f:Resumable<'a> * g:('a -> Resumable<'b>) -> Resumable<'a option,'b>
member BindNoCache : f:Resumable<'a> * g:('a -> Resumable<'b>) -> Resumable<'b>
member Combine : p1:Resumable<'u,unit> * p2:Resumable<'v,'b> -> Resumable<('u * 'v),'b>
member Combine : p1:Resumable<unit> * p2:Resumable<'b> -> Resumable<'b>
member Delay : f:(unit -> Resumable<'h,'t>) -> Resumable<'h,'t>
member Delay : f:(unit -> Resumable<'t>) -> Resumable<'t>
...
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder
The syntax builder for the Resumable monadic syntax
--------------------
new : unit -> ResumableBuilder
member ResumableBuilder.Zero : unit -> Resumable<unit>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Zero
val __ : ResumableBuilder
member ResumableBuilder.Return : x:'t -> Resumable<'t>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Return
val x : 't
member ResumableBuilder.Delay : f:(unit -> Resumable<'h,'t>) -> Resumable<'h,'t>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Delay
val f : (unit -> Resumable<'h,'t>)
val p : Persist<'h>
member ResumableBuilder.Delay : f:(unit -> Resumable<'t>) -> Resumable<'t>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Delay
val f : (unit -> Resumable<'t>)
member ResumableBuilder.Bind : f:Resumable<'u,'a> * g:('a -> Resumable<'v,'b>) -> Resumable<('a option * 'u * 'v),'b>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Bind
val f : Resumable<'u,'a>
val g : ('a -> Resumable<'v,'b>)
val p : Persist<'a option * 'u * 'v>
val cached : 'a option
val u : 'u
val v : 'v
val persista : Persist<'a>
val a : 'a
val persistu : Persist<'u>
member Resumable.resume : persist:Persist<'h> -> h:'h -> 't
Resume from a specified history of caching points
member ResumableBuilder.Bind : f:Resumable<'u,'a> * g:('a -> Resumable<'b>) -> Resumable<('a option * 'u),'b>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Bind
val g : ('a -> Resumable<'b>)
val p : Persist<'a option * 'u>
member ResumableBuilder.Bind : f:Resumable<'a> * g:('a -> Resumable<'v,'b>) -> Resumable<('a option * 'v),'b>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Bind
val f : Resumable<'a>
val p : Persist<'a option * 'v>
member Resumable.resume : unit -> 't
member ResumableBuilder.Bind : f:Resumable<'a> * g:('a -> Resumable<'b>) -> Resumable<'a option,'b>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Bind
val p : Persist<'a option>
member ResumableBuilder.BindNoCache : f:Resumable<'a> * g:('a -> Resumable<'b>) -> Resumable<'b>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.BindNoCache
member ResumableBuilder.Combine : p1:Resumable<'u,unit> * p2:Resumable<'v,'b> -> Resumable<('u * 'v),'b>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Combine
val p1 : Resumable<'u,unit>
val p2 : Resumable<'v,'b>
val p : Persist<'u * 'v>
member ResumableBuilder.Combine : p1:Resumable<unit> * p2:Resumable<'b> -> Resumable<'b>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.Combine
val p1 : Resumable<unit>
val p2 : Resumable<'b>
member ResumableBuilder.While : condition:(unit -> bool) * body:Resumable<unit> -> Resumable<unit>
Full name: ResumableMonad.MultipstepPersisted.ResumableBuilder.While
val condition : (unit -> bool)
val body : Resumable<unit>
member ResumableBuilder.BindNoCache : f:Resumable<'a> * g:('a -> Resumable<'b>) -> Resumable<'b>
member ResumableBuilder.While : condition:(unit -> bool) * body:Resumable<unit> -> Resumable<unit>
member ResumableBuilder.Zero : unit -> Resumable<unit>
val resumable : ResumableBuilder
Full name: ResumableMonad.MultipstepPersisted.resumable
val NewtonsoftStorage<'T> : fileName:string -> CacheStorage<obj>
Full name: ResumableMonad.MultipstepPersisted.NewtonsoftStorage
File storage based on Newtonsoft serialization
val fileName : string
val history : obj
namespace System
namespace System.IO
type File =
static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
static member AppendAllText : path:string * contents:string -> unit + 1 overload
static member AppendText : path:string -> StreamWriter
static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
static member Create : path:string -> FileStream + 3 overloads
static member CreateText : path:string -> StreamWriter
static member Decrypt : path:string -> unit
static member Delete : path:string -> unit
static member Encrypt : path:string -> unit
static member Exists : path:string -> bool
...
Full name: System.IO.File
System.IO.File.WriteAllText(path: string, contents: string) : unit
System.IO.File.WriteAllText(path: string, contents: string, encoding: System.Text.Encoding) : unit
System.IO.File.Exists(path: string) : bool
System.IO.File.ReadAllText(path: string) : string
System.IO.File.ReadAllText(path: string, encoding: System.Text.Encoding) : string
val LuStorage<'T> : fileName:string -> CacheStorage<int option * (int option * int option)>
Full name: ResumableMonad.MultipstepPersisted.LuStorage
File storage based on FSharpLu.Json
namespace Microsoft
val cache : Choice<(int option * (int option * int option)),obj>
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
val cache : int option * (int option * int option)
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
module Example
from ResumableMonad.MultipstepPersisted
val isPrime : _arg1:int -> bool
Full name: ResumableMonad.MultipstepPersisted.Example.isPrime
val n : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
type Math =
static val PI : float
static val E : float
static member Abs : value:sbyte -> sbyte + 6 overloads
static member Acos : d:float -> float
static member Asin : d:float -> float
static member Atan : d:float -> float
static member Atan2 : y:float * x:float -> float
static member BigMul : a:int * b:int -> int64
static member Ceiling : d:decimal -> decimal + 1 overload
static member Cos : d:float -> float
...
Full name: System.Math
Math.Ceiling(a: float) : float
Math.Ceiling(d: decimal) : decimal
Math.Sqrt(d: float) : float
Multiple items
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
module Seq
from Microsoft.FSharp.Collections
val forall : predicate:('T -> bool) -> source:seq<'T> -> bool
Full name: Microsoft.FSharp.Collections.Seq.forall
val d : int
val nthprime : n:int -> int
Full name: ResumableMonad.MultipstepPersisted.Example.nthprime
val p : int
val initInfinite : initializer:(int -> 'T) -> seq<'T>
Full name: Microsoft.FSharp.Collections.Seq.initInfinite
val id : x:'T -> 'T
Full name: Microsoft.FSharp.Core.Operators.id
val where : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Collections.Seq.where
val item : index:int -> source:seq<'T> -> 'T
Full name: Microsoft.FSharp.Collections.Seq.item
val addPrimes : n1:int -> n2:int -> n3:int -> Resumable<(int option * (int option * int option)),int>
Full name: ResumableMonad.MultipstepPersisted.Example.addPrimes
val n1 : int
val n2 : int
val n3 : int
val p1 : int
type Console =
static member BackgroundColor : ConsoleColor with get, set
static member Beep : unit -> unit + 1 overload
static member BufferHeight : int with get, set
static member BufferWidth : int with get, set
static member CapsLock : bool
static member Clear : unit -> unit
static member CursorLeft : int with get, set
static member CursorSize : int with get, set
static member CursorTop : int with get, set
static member CursorVisible : bool with get, set
...
Full name: System.Console
Console.Read() : int
val ignore : value:'T -> unit
Full name: Microsoft.FSharp.Core.Operators.ignore
val p2 : int
val p3 : int
val sum : int
val f : string
Full name: ResumableMonad.MultipstepPersisted.Example.f
type Path =
static val DirectorySeparatorChar : char
static val AltDirectorySeparatorChar : char
static val VolumeSeparatorChar : char
static val InvalidPathChars : char[]
static val PathSeparator : char
static member ChangeExtension : path:string * extension:string -> string
static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
static member GetDirectoryName : path:string -> string
static member GetExtension : path:string -> string
static member GetFileName : path:string -> string
...
Full name: System.IO.Path
IO.Path.GetTempFileName() : string
IO.File.Delete(path: string) : unit
val result : int
Full name: ResumableMonad.MultipstepPersisted.Example.result
val printf : format:Printf.TextWriterFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
IO.File.ReadAllText(path: string) : string
IO.File.ReadAllText(path: string, encoding: Text.Encoding) : string