You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -10,54 +10,27 @@ Perhaps the most important field in the application record
10
10
11
11
```haskell
12
12
typeApplicationmodelmessage= {
13
-
init::model,
13
+
model::model
14
14
view::model->Htmlmessage,
15
-
update::model->message->model,
15
+
update::Updatemodelmessage,
16
16
subscribe::Array (Subscriptionmessage)
17
17
}
18
18
```
19
19
20
-
is the `update` function. This is where we define our business logic by matching event `message`s and returning an updated model. For simplicity, we have only considered side effects free updating so far, however Flame offers three different ways to define your `update` function.
21
-
22
-
Each module under `Flame.Application` export a `mount` (or `mount_`) function which asks for an application record with different `init` and `update` types
23
-
24
-
```haskell
25
-
importFlame.Application.NoEffects (mount) -- side effects free updating
26
-
importFlame (mount) -- Elm style updating, using a list of effects
27
-
importFlame.Application.Effectful (mount) -- Aff based updating
28
-
```
29
-
30
-
Let's discuss each of them in detail.
31
-
32
-
### No effects updating
33
-
34
-
The application record is the same as we have seen so far
20
+
is the `update` function. This is where we define our business logic by matching event `message`s and returning an updated model. So far we have been using the `Update` type alias. Let's expand it:
35
21
36
22
```haskell
37
23
typeApplicationmodelmessage= {
38
-
init::model,
39
-
view::model->Htmlmessage,
40
-
update::model->message->model,
41
-
subscribe::Array (Subscriptionmessage)
42
-
}
43
-
```
44
-
45
-
This is enough for toy examples or small modules, but probably not sufficient to build an user facing application. If we want to do any sort of effectul computation we need to look into the next `update` types.
46
-
47
-
### Effect list updating
48
-
49
-
This is the default way to run a Flame application. The record here has `init` and `update` return an array of effects to be performed
For every entry in the array, the effect is run and `update` is called again with the resulting `message`. Consider an application to roll dices
31
+
Besides the updated model, `update` also returns an array of side effects to perform. Each entry in the array may optionally raise another `message`, which in turn is handled by `update` as well.
32
+
33
+
Consider an application to roll dices
61
34
62
35
```haskell
63
36
typeModel=MaybeInt
@@ -66,10 +39,11 @@ data Message = Roll | Update Int
Whenever `update` receives the `Roll` message, a `Tuple` (using the infix operator `/\`) of the model and effect list is returned. Performing the effect in the list raises the `Update` message, which carries the generated random number that will be the new model.
55
+
`Roll` returns the model as it is. However, generating random numbers is a side effect so we return it on our array. Flame will run this effect and raise `Update`, which then updates the model with the die number.
82
56
83
57
Likewise, we could define a loading screen to appear before AJAX requests
84
58
@@ -103,143 +77,32 @@ useResponse = ...
103
77
useDifferentResponse::Model->String->AffModel
104
78
useDifferentResponse =...
105
79
106
-
update::UpdateModelMessage-- type synonym to reduce clutter
In the same way, here every call to `performAJAX` also causes `update` to be called again with a new `Response` or `DifferentResponse` until we get a `Finish` message.
129
-
130
-
Notice that the type of `init` is also defined as `Tuple model (Array (Aff (Maybe message)))`. This enables us to run effects at the startup of the application. Suppose in the previous example we also wanted to perform some AJAX requests before any other user interaction. We could have defined `init` as follows
131
-
132
-
```haskell
133
-
init::TupleModel (Array (Aff (MaybeMessage)))
134
-
init= model /\ [
135
-
Just<<<Response<$> performAJAX "url",
136
-
Just<<<DifferentResponse<$> performAJAX "url2",
137
-
Just<<<Response<$> performAJAX "url3",
138
-
Just<<<DifferentResponse<$> performAJAX "url4",
139
-
pure<<<Just$Finish"Performed all"
140
-
]
141
-
```
142
-
143
-
which has the same expected behavior of calling `update` with the resulting message of every entry in the array.
144
-
145
-
### Effectful updating
146
-
147
-
Returning an array of effects is great for testability and isolating input/output, but certain program flows became somewhat awkward to write. For most messages, we essentially have to create a different data constructor for each step in the computation. For that reason, Flame provides an alternative way to perform effects in the `update` function.
Here instead of returning a list of effects, we perform them directly in the `Aff` monad. Because the `update` function is now fully asynchronous, its type is a little different. Instead of the model, we return a function to modify it -- this ensures slower computations don't overwrite unrelated updates that might happen in the meanwhile. `Environment` is defined as follows
161
-
162
-
```haskell
163
-
typeEnvironmentmodelmessage= {
164
-
model::model,
165
-
message::message,
166
-
display:: (model->model) ->AffUnit
167
-
}
168
-
```
169
-
170
-
`model` and `message` are now grouped in a record. `display` is a function to arbitrarily re-render the view.
171
-
172
-
Let's rewrite the dice application using the effectful updates
Since we are always generating a new model, and don't need an intermediate message to update it, we can ignore the environment and perform the update in a single go.
184
-
185
-
Let's see how we can use `display` to rewrite the AJAX example from above as well
186
-
187
-
```haskell
188
-
typeModel= {
189
-
response::String,
190
-
isLoading::boolean
191
-
}
192
-
193
-
dataMessage=Loading
194
-
195
-
update::UpdateModelMessage-- type synonym to reduce clutter
196
-
update { display } =do
197
-
display _ { isLoading = true }
198
-
traverse (\rs -> display _ { response = rs }) [
199
-
performAJAX "url",
200
-
performAJAX "url2",
201
-
performAJAX "url3",
202
-
performAJAX "url4",
203
-
]
204
-
pure$ _ { isLoading = false }
205
-
206
-
init::TupleModel (MaybeMessage)
207
-
init= model /\JustLoading
208
-
```
209
-
210
-
`display` renders the view with the modified model without leaving the `update` function, which is again a little more straightforward.
211
-
212
-
But juggling model update functions can quickly turn messy, specially if we are using records. For that reason, helper functions are provided to modify only given fields
the `Diff` type class guarantees that `changed` only includes fields present in`model` so instead of `pure _ { field = value }` we can write `diff { field: value }`.Let's see an example:
Here for `Loading`, we return an array of network calls and a final `Finish` message. The effects are run in order, and once we have a response, their events are raised for `update` as well.
241
104
242
-
Here, no matter how many fields `MyModel` has, we update only what's required in each case expression. Notice that `diff` always takes a record as first parameter. The model, however, can be either a record or newtype (given a `Newtype` instance)/plain functor that holds a record to be updated.
105
+
You may be wondering: why separate model updating and side effects? The reason is that in this way we are "forced" to keep most of our business logic in pure functions, which are easier to reason and test. Effects become interchangeable, decoupled from what we do with their results.
243
106
244
107
## [Subscriptions](#subscriptions)
245
108
@@ -250,7 +113,7 @@ More often than not, a real world application needs to handle events that don't
250
113
When mounting the application, `subscribe` can be used to specify `message`s as a list of subscriptions. The modules under `Flame.Subscription` define `on` event handlers similar to those used in views
251
114
252
115
```haskell
253
-
FAN.mount_ (QuerySelector"body") {
116
+
F.mount_ (QuerySelector"body") {
254
117
...
255
118
subscribe: [
256
119
FSW.onLoad Message, -- `window` event from `Flame.Subscription.Window`
@@ -272,7 +135,7 @@ Once a subscription has been defined, the raised `message` will be handled by th
272
135
273
136
*Arbitrary message passing
274
137
275
-
Sometimes, we need to talk to an application from external events handlers or other points in code far away from the mount point.Consider an app that uses web sockets, or a singe page application that uses multiple mount points for lazy loading.For these and other use cases, Flame provides a `mount` function that takes an application id, as well a `send` function to raise messages for application ids
138
+
Sometimes, we need to talk to an application from external events handlers or other points inthe code away from the mount point.Consider an app that uses web sockets, or a singe page application that uses multiple mount points for lazy loading, or just simple initialization events.For these and other use cases, Flame provides a `mount` (no trailing underscore) function that takes an application id, as well a `send` function to raise messages for application ids
0 commit comments