Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/chadtech/return
(DEPRECIATED) Helpers for update function return values
https://github.com/chadtech/return
elm tuples
Last synced: 28 days ago
JSON representation
(DEPRECIATED) Helpers for update function return values
- Host: GitHub
- URL: https://github.com/chadtech/return
- Owner: Chadtech
- License: other
- Created: 2018-04-02T01:21:38.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2019-03-02T13:27:53.000Z (almost 6 years ago)
- Last Synced: 2024-11-06T04:40:57.779Z (3 months ago)
- Topics: elm, tuples
- Language: Elm
- Homepage: http://package.elm-lang.org/packages/Chadtech/return/latest/
- Size: 17.6 KB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
- License: LICENSE
Awesome Lists containing this project
README
## Depreciated, sort of
This code still works, but it represents a technique of scaling Elm applications that I think is dated at this point (March 2nd, 2019). Nested state used to be more common in Elm, but now Elm people seem to have learned that nesting state is really undesirable. This package represents how I would handle nested state, but the purpose of this package is strongly undermined by the fact that now I just wouldnt nest state to begin with. So, if you wish, I hope this package can serve you well! Maybe [a similar package from a true believer in this approach would be better](https://package.elm-lang.org/packages/z5h/component-result/latest/). But, ultimately, I think you probably shouldnt need to do any of this stuff.
Heres some code that shows what I mean..
```elm
-- The Before Times
-- parent model has gloally things, and passes them down into child-y things-- Parent <---------- handles updates ----------
-- | |
-- passes global stuff down passes it back up
-- | |
-- v |
-- Child ---------- updates globally stuff -----type alias Model =
{ page : Page
, user : User
}type Page
= Home Home.Model
| Login Login.Modeltype ExternalMsg
= SetUser User-- Login.elm
update : User -> Login.Msg -> Login.Model -> (Login.Model, Cmd Login.Msg, ExternalMsg)-- Today
-- Totally flat model, model states pass off globaly stuff to each other
-- only rule is that all states possess same globaly stuff, and have
-- methods for accepting and giving global state
type Model
= Home Home.Model
| Login Login.ModelgetUser : Model -> User
getUser model =
case model of
Login loginModel ->
loginModel.userHome homeModel ->
homeModel.usergoToLoginPage : Model -> Model
goToLoginPage model =
Login.init <| getUser model-- Login.elm
init : User -> Login.Model
update : Login.Msg -> Login.Model -> (Login.Model, Cmd Login.Msg)
```Everything below is the original readme preserved for posterity.
# Return
This package is for handling the subtle architectural problems associated with nested update functions in big Elm applications. There are no simple principles at work here; this package is just a product of my cumulated experience from writing a lot Elm and paying attention to what other people do when they write Elm. Immediately below is a code example, and further below is an explanation of how I got here.
## Example
```elm
-- Main.elm --
import Login
import Return2 as R2
import Return3 as R3type alias Model =
{ page : Page
, user : Maybe User
}type Page
= Login Login.Model
| Hometype Msg
= LoginMsg Login.Msgupdate : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LoginMsg subMsg ->
case model.page of
Login subModel ->
subModel
|> Login.update subMsg
|> R3.mapCmd LoginMsg
|> R3.incorp handleLoginReply model_ ->
model
|> R2.withNoCmdhandleLoginReply : Login.Model -> Maybe Login.Reply -> Model -> (Model, Cmd Msg)
handleLoginReply subModel maybeReply model =
case maybeReply of
Nothing ->
model
|> setPage Login subModel
|> R2.withNoCmdJust (Login.UserLoggedIn user) ->
{ model
| page = Home
, user = Just user
}
|> R2.withNoCmdsetPage : (subModel -> Page) -> subModel -> Model -> Model
setPage pageCtor subModel model =
{ model | page = pageCtor subModel }-- Login.Elm --
import Return3 as R3 exposing (Return)
import Return2 as R2type alias Model =
{ field : String }type Msg
= FieldUpdated String
| SubmitClicked
| LoginSucceeded Usertype Reply
= UserLoggedIn Userupdate : Msg -> Model -> Return Model Msg Reply
update msg model =
case msg of
FieldUpdated str ->
{ model | field = str }
|> R3.withNothingSubmitClicked ->
model
|> R2.withCmd (submitRequest model)
|> R3.withNoReplyLoginSucceeded user ->
model
|> R2.withNoCmd
|> R3.withReply (UserLoggedIn user)
```## Explanation
In Elm, our update functions return a tuple containing our `Model` and whatever side effects we want to occur outside the Elm run-time.
```elm
( Model, Cmd Msg )
```
In practice, that means we have to type stuff like this.
```elm
( editThing model, thingCmd (getData model))
```
Unfortunately this turns out to be not so nice. We have to type this tuple a lot, and doing so is a hassle because there are characters on the left, right, and center of the tuple you have to type. It would be much easier if you only had to put your cursor in one spot rather than jump around. As an alternative some of us began using our own infix operators; most notably the folks at NoRedInk use the ["rocket" operator](http://package.elm-lang.org/packages/NoRedInk/rocket-update/latest).
```elm
model => thingCmd(=>) : a -> b -> (a, b)
(=>) model cmd =
(model, cmd)
```
Thats pretty good. But as I write this, the expectation is that the upcoming Elm 0.19 wont allow custom infix operators (which is probably for the best). We will need a new solution. I really like [Janiczek/cmd-extra](http://package.elm-lang.org/packages/Janiczek/cmd-extra/latest). His package exposes some extremely readable functions, like `withCmd`.
```elm
model
|> withCmd cmdwithCmd : Cmd msg -> model -> (model, Cmd msg)
```
Its a little bit more verbose, but it reads really nice and it plays well with whatever else is going on in your update function. If this were the end of the story, I would just settle on Janiczek's package, but its not. Big Elm applications usually have sub-update functions, sub-models, and sub-msgs. The communication between parent and child update functions can be kind of complicated, and often we need to transform values after they have been bundled into tuples. Our update function technique has to work well in the broader context of what update functions are doing in our application.Richard Feldman uses the `ExternalMsg` type for communication from child update functions to parent update functions. His update functions result in something that looks like this.
```elm
((Model, Cmd Msg), ExternalMsg)
```
The `ExternalMsg` carries information from the sub module to its parent module. The best example of this is having a login page with its own update function, that needs to tell the main update function who the user is once they successfully log in. The sub update function in your login page doesnt just return a sub-model and sub-cmds, it also returns information that the application as a whole needs to know, like that the user session has changed.That works very well. One criticism I have however, is nested tuples are kind of hard to work with. The model is in a tuple in a tuple. So if you want to access it- say to transform it- you need to do `Tuple.first >> Tuple.first`. Flat data structures are nicer, so I have learned to do triples instead.
```elm
(Model, Cmd Msg, ExternalMsg)
```
Also, I found that the name `ExternalMsg` didnt make much sense. Regular `Msg`s reflect external events that actually happened in your application that have indeterminate consequence. Stuff like mouse clicks, or http responses; your application just knows what happened and thereafter needs to figure out what to do. `ExternalMsg`s arent the same kind of thing despite what the name implies. They represent interal results from within your application which usually have explicit consequence. I instead started calling them `Reply` since they are like replies to the news sub-modulereceive from the parent-modules. Also I made it into a `Maybe Reply`, so you can consider the possibility of no reply abstractly, without assuming any particular `Reply` type.
```elm
(Model, Cmd Msg, Maybe Reply)
```
Improvements from this point are harder and more tenuous, but I have also learned a bit from [Fresheyeball/elm-return](http://package.elm-lang.org/packages/Fresheyeball/elm-return/6.0.3/) as well. Sub-models need to be incorporated back into their parent-models, and usually in very regular and predictable ways, such as just being a field inside a record. Fresheyeball's package exposes functions that simplify that incorporation process. Unfortunately, I think Fresheyeball's package indulges a lot of functional programming stuff beyond its usefulness (and it uses infix operators, so its usefulness wont last into 0.19). But regardless, his approach to formalizing and mutating return results is a good one that I have tried to reproduce in this package.