-
Notifications
You must be signed in to change notification settings - Fork 1
/
program.fs
136 lines (115 loc) · 5.08 KB
/
program.fs
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
(*
Copyright 2016 fable-elmish contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*)
(*
In accordance with the Apache License, Version 2.0, section 4, this is a
notice to inform the recipient that this file has been modified by the
elmish-xamarin-forms contributors.
*)
(**
Program
---------
Core abstractions for creating and running the dispatch loop.
*)
namespace Elmish
open System
/// Program type captures various aspects of program behavior
type Program<'arg, 'model, 'msg, 'view> = {
init : 'arg -> 'model * Cmd<'msg>
update : 'msg -> 'model -> 'model * Cmd<'msg>
subscribe : 'model -> Cmd<'msg>
view : 'model -> Dispatch<'msg> -> 'view
setState : 'model -> Dispatch<'msg> -> unit
onError : (string*exn) -> unit
}
/// Program module - functions to manipulate program instances
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Program =
let internal onError (text: string, ex: exn) =
System.Console.Error.WriteLine (sprintf "%s: %A" text ex)
/// Typical program, new commands are produced by `init` and `update` along with the new state.
let mkProgram
(init : 'arg -> 'model * Cmd<'msg>)
(update : 'msg -> 'model -> 'model * Cmd<'msg>)
(view : 'model -> Dispatch<'msg> -> 'view) =
{ init = init
update = update
view = view
setState = fun model -> view model >> ignore
subscribe = fun _ -> Cmd.none
onError = onError }
/// Simple program that produces only new state with `init` and `update`.
let mkSimple
(init : 'arg -> 'model)
(update : 'msg -> 'model -> 'model)
(view : 'model -> Dispatch<'msg> -> 'view) =
{ init = init >> fun state -> state,Cmd.none
update = fun msg -> update msg >> fun state -> state,Cmd.none
view = view
setState = fun model -> view model >> ignore
subscribe = fun _ -> Cmd.none
onError = onError }
/// Subscribe to external source of events.
/// The subscription is called once - with the initial model, but can dispatch new messages at any time.
let withSubscription (subscribe : 'model -> Cmd<'msg>) (program: Program<'arg, 'model, 'msg, 'view>) =
let sub model =
Cmd.batch [ program.subscribe model
subscribe model ]
{ program with subscribe = sub }
/// Trace all the updates to the console
let withConsoleTrace (program: Program<'arg, 'model, 'msg, 'view>) =
let traceInit (arg:'arg) =
let initModel,cmd = program.init arg
System.Console.Out.WriteLine (sprintf "Initial state: %A" initModel)
initModel,cmd
let traceUpdate msg model =
System.Console.Out.WriteLine (sprintf "New message: %A" msg)
let newModel,cmd = program.update msg model
System.Console.Out.WriteLine (sprintf "Updated state: %A" newModel)
newModel,cmd
{ program with
init = traceInit
update = traceUpdate }
/// Trace all the messages as they update the model
let withTrace trace (program: Program<'arg, 'model, 'msg, 'view>) =
{ program
with update = fun msg model -> trace msg model; program.update msg model}
/// Handle dispatch loop exceptions
let withErrorHandler onError (program: Program<'arg, 'model, 'msg, 'view>) =
{ program
with onError = onError }
/// Start the program loop.
/// arg: argument to pass to the init() function.
/// program: program created with 'mkSimple' or 'mkProgram'.
let runWith (arg: 'arg) (program: Program<'arg, 'model, 'msg, 'view>) =
let (model,cmd) = program.init arg
let inbox = MailboxProcessor.Start(fun (mb:MailboxProcessor<'msg>) ->
let rec loop (state:'model) =
async {
let! msg = mb.Receive()
try
let (model',cmd') = program.update msg state
program.setState model' mb.Post
cmd' |> List.iter (fun sub -> sub mb.Post)
return! loop model'
with ex ->
program.onError ("Unable to process a message:", ex)
return! loop state
}
loop model
)
program.setState model inbox.Post
program.subscribe model
@ cmd |> List.iter (fun sub -> sub inbox.Post)
/// Start the dispatch loop with `unit` for the init() function.
let run (program: Program<unit, 'model, 'msg, 'view>) = runWith () program