Skip to content

Commit 6bf9d85

Browse files
committed
Update docs
1 parent b5435ae commit 6bf9d85

File tree

5 files changed

+58
-197
lines changed

5 files changed

+58
-197
lines changed

docs/concepts.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ A Flame application consists of the following record
1010

1111
```haskell
1212
type Application model message = {
13-
init :: model,
13+
model :: model,
1414
view :: model -> Html message,
15-
update :: model -> message -> model,
15+
update :: Update model message,
1616
subscribe :: Array (Subscription message)
1717
}
1818
```
@@ -31,11 +31,11 @@ that is, the state of our application is a single integer. In a real world appli
3131
With our model type declared, we can define the initial state of the application
3232

3333
```haskell
34-
init :: Model
35-
init = 0
34+
model :: Model
35+
model = 0
3636
```
3737

38-
The first time the application is rendered, Flame calls the view function with `init`.
38+
The first time the application is rendered, Flame calls the view function with `model`.
3939

4040
### Application markup
4141

@@ -67,13 +67,13 @@ data Message = Increment | Decrement
6767
and thus our update function looks like
6868

6969
```haskell
70-
update :: Model -> Message -> Model
70+
update :: Update Model Message
7171
update model = case _ of
72-
Increment -> model + 1
73-
Decrement -> model - 1
72+
Increment -> model + 1 /\ []
73+
Decrement -> model - 1 /\ []
7474
```
7575

76-
See [Handling events](events) for an in depth look at update strategies.
76+
See [Handling events](events) for an in depth look at updates.
7777

7878
### Subscriptions
7979

@@ -84,19 +84,18 @@ In the counter example no external events are handled, so the subscription list
8484
```haskell
8585
subscribe :: Array (Subscription Message)
8686
subscribe = []
87-
}
8887
```
8988

90-
See [Handling external events](events#handling-external-events) for an in depth look at subscriptions.
89+
See [Subscriptions](events#subscriptions) for an in depth look at subscriptions.
9190

9291
### Rendering
9392

9493
Having all pieces put together, we can either render the application to the DOM, as in the case of the counter example
9594

9695
```haskell
9796
main :: Effect Unit
98-
main = FAN.mount_ (QuerySelector "body") {
99-
init,
97+
main = F.mount_ (QuerySelector "body") {
98+
model,
10099
view,
101100
update,
102101
subscribe

docs/events.md

Lines changed: 27 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,27 @@ Perhaps the most important field in the application record
1010

1111
```haskell
1212
type Application model message = {
13-
init :: model,
13+
model :: model
1414
view :: model -> Html message,
15-
update :: model -> message -> model,
15+
update :: Update model message,
1616
subscribe :: Array (Subscription message)
1717
}
1818
```
1919

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-
import Flame.Application.NoEffects (mount) -- side effects free updating
26-
import Flame (mount) -- Elm style updating, using a list of effects
27-
import Flame.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:
3521

3622
```haskell
3723
type Application model message = {
38-
init :: model,
39-
view :: model -> Html message,
40-
update :: model -> message -> model,
41-
subscribe :: Array (Subscription message)
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
50-
51-
```haskell
52-
type Application model message = {
53-
init :: Tuple model (Array (Aff (Maybe message))),
24+
model :: model,
5425
view :: model -> Html message,
5526
update :: model -> message -> Tuple model (Array (Aff (Maybe message))),
5627
subscribe :: Array (Subscription message)
5728
}
5829
```
5930

60-
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
6134

6235
```haskell
6336
type Model = Maybe Int
@@ -66,10 +39,11 @@ data Message = Roll | Update Int
6639

6740
update :: Model -> Message -> Tuple Model (Array (Aff (Maybe Message)))
6841
update model = case _ of
69-
Roll -> model /\ [
70-
Just <<< Update <$> liftEffect (ER.randomInt 1 6)
71-
]
42+
Roll -> model /\ [ rollDice ]
7243
Update int -> Just int /\ []
44+
where rollDice = do
45+
n <- EC.liftEffect $ ER.randomInt 1 6
46+
pure <<< Just $ Update n
7347

7448
view :: Model -> Html Message
7549
view model = HE.main "main" [
@@ -78,7 +52,7 @@ view model = HE.main "main" [
7852
]
7953
```
8054

81-
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.
8256

8357
Likewise, we could define a loading screen to appear before AJAX requests
8458

@@ -103,143 +77,32 @@ useResponse = ...
10377
useDifferentResponse :: Model -> String -> Aff Model
10478
useDifferentResponse = ...
10579

106-
update :: Update Model Message -- type synonym to reduce clutter
80+
update :: Model -> Message -> Tuple Model (Array (Aff (Maybe Message)))
10781
update model = case _ of
108-
Loading -> model { isLoading = true } /\ [
109-
Just <<< Response <$> performAJAX "url",
110-
Just <<< DifferentResponse <$> performAJAX "url2",
111-
Just <<< Response <$> performAJAX "url3",
112-
Just <<< DifferentResponse <$> performAJAX "url4",
113-
pure <<< Just $ Finish "Performed all"
114-
]
82+
Loading -> model { isLoading = true } /\ requests
11583
Response contents -> F.noMessages $ useResponse model contents -- noMessages is the same as _ /\ []
116-
Finish contents -> F.noMessages $ model { isLoading = false, response = model.response <> contents }
84+
Finish contents -> F.noMessages model { isLoading = false, response = model.response <> contents }
85+
where requests = [
86+
Just <<< Response <$> performAJAX "url",
87+
Just <<< DifferentResponse <$> performAJAX "url2",
88+
Just <<< Response <$> performAJAX "url3",
89+
Just <<< DifferentResponse <$> performAJAX "url4",
90+
pure <<< Just $ Finish "Performed all"
91+
]
11792

11893
view :: Model -> Html Message
11994
view model = HE.main "main" [
12095
HE.button [HA.disabled model.isLoading, HA.onClick Loading] "Perform requests",
12196
if model.isLoading then
122-
HE.div [HA.className "overlay"] "Loading..."
97+
HE.div [HA.class' "overlay"] "Loading..."
12398
else
12499
...
125100
]
126101
```
127102

128-
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 :: Tuple Model (Array (Aff (Maybe Message)))
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.
148-
149-
The effectful updating defines `Application` as
150-
151-
```haskell
152-
type Application model message = {
153-
init :: Tuple model (Maybe message),
154-
view :: model -> Html message,
155-
update :: Environment model message -> Aff (model -> model),
156-
subscribe :: Array (Subscription message)
157-
}
158-
```
159-
160-
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-
type Environment model message = {
164-
model :: model,
165-
message :: message,
166-
display :: (model -> model) -> Aff Unit
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
173-
174-
```haskell
175-
type Model = Maybe Int
176-
177-
data Message = Roll
178-
179-
update :: Environment Model Message -> Aff (Model -> Model)
180-
update _ = map (const <<< Just) $ liftEffect $ ER.randomInt 1 6
181-
```
182-
183-
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-
type Model = {
189-
response :: String,
190-
isLoading :: boolean
191-
}
192-
193-
data Message = Loading
194-
195-
update :: Update Model Message -- 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 :: Tuple Model (Maybe Message)
207-
init = model /\ Just Loading
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
213-
214-
```haskell
215-
diff' :: forall changed model. Diff changed model => changed -> (model -> model)
216-
diff :: forall changed model. Diff changed model => changed -> Aff (model -> model)
217-
```
218-
219-
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:
220-
221-
```haskell
222-
newtype MyModel = MyModel {
223-
url :: String,
224-
result :: Result,
225-
...
226-
}
227-
derive instance myModelNewtype :: Newtype MyModel _
228-
229-
update :: Update MyModel Message
230-
update { display, model: MyModel model, message } =
231-
case message of
232-
UpdateUrl url -> F.diff { url, result: NotFetched }
233-
Fetch -> do
234-
display $ F.diff' { result: Fetching }
235-
response <- A.get AR.string model.url
236-
F.diff <<< { result: _ } $ case response.body of
237-
Left error -> Error $ A.printResponseFormatError error
238-
Right ok -> Ok ok
239-
... -> ...
240-
```
103+
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.
241104

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.
243106

244107
## [Subscriptions](#subscriptions)
245108

@@ -250,7 +113,7 @@ More often than not, a real world application needs to handle events that don't
250113
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
251114

252115
```haskell
253-
FAN.mount_ (QuerySelector "body") {
116+
F.mount_ (QuerySelector "body") {
254117
...
255118
subscribe: [
256119
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
272135

273136
* Arbitrary message passing
274137

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 in the 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
276139

277140
```haskell
278141
mount :: forall id model message. Show id => QuerySelector -> AppId id message -> Application model message -> Effect Unit

docs/index.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ module Counter.Main where
3333
import Prelude
3434

3535
import Effect (Effect)
36-
import Flame (Html, QuerySelector(..), Subscription)
37-
-- Side effects free updating; see docs for other examples
38-
import Flame.Application.NoEffects as FAN
36+
import Web.DOM.ParentNode (QuerySelector(..))
37+
import Flame (Html, Update, Subscription)
38+
import Flame as F
3939
import Flame.Html.Element as HE
4040
import Flame.Html.Attribute as HA
4141

@@ -46,14 +46,14 @@ type Model = Int
4646
data Message = Increment | Decrement
4747

4848
-- | Initial state of the app
49-
init :: Model
50-
init = 0
49+
model :: Model
50+
model = 0
5151

5252
-- | `update` is called to handle events
53-
update :: Model -> Message -> Model
53+
update :: Update Model Message
5454
update model = case _ of
55-
Increment -> model + 1
56-
Decrement -> model - 1
55+
Increment -> model + 1 /\ []
56+
Decrement -> model - 1 /\ []
5757

5858
-- | `view` is called whenever the model is updated
5959
view :: Model -> Html Message
@@ -69,8 +69,8 @@ subscribe = []
6969

7070
-- | Mount the application on the given selector
7171
main :: Effect Unit
72-
main = FAN.mount_ (QuerySelector "body") {
73-
init,
72+
main = F.mount_ (QuerySelector "body") {
73+
model,
7474
view,
7575
update,
7676
subscribe

0 commit comments

Comments
 (0)