Last active
November 4, 2017 09:53
-
-
Save toretore/10965b07bd17d8e9f7469d269e2a3e69 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-------------------------------------------------------------------------------- | |
-- The `msg` in `Html msg` and `Cmd msg` can be anything | |
-------------------------------------------------------------------------------- | |
-- Most Elm apps will have a `Msg` type at the top lever describing the messages | |
-- that can be processed by `update`: | |
type Msg | |
= UserLoaded (Maybe User) | |
| UserSelected User | |
-- If you have some `User` module containing logic for displaying and fetching | |
-- users, you don't have to import and use this type to produce `Html Msg` or | |
-- `Cmd Msg`, but instead produce e.g. `Html User` and `Cmd User`: | |
viewUser : User -> Html User | |
viewUser user = | |
div [onClick user] [text (user.first ++ " " ++ user.last)] | |
fetchUser : Int -> Cmd (Maybe User) | |
fetchUser id = | |
Http.get userDecoder ("/users/" ++ (toString id) ++ ".json") | |
|> Http.send Result.toMaybe | |
-- Then, at the top level, you `map` these values into the required types: | |
view : Model -> Html Msg | |
view {users} = | |
div [class "users"] (List.map (User.viewUser >> Html.map UserSelected) users) | |
init : (Model, Cmd Msg) | |
init = (initModel, Users.fetchUser |> Cmd.map UserLoaded) | |
-------------------------------------------------------------------------------- | |
-- Use `andThen` when you run out of `Json.Decode.mapN`s | |
-------------------------------------------------------------------------------- | |
-- Here we have a record with 10 fields, resulting in a `User` constructor | |
-- that takes 10 arguments. `map10` doesn't exist, so how do we make a `Decoder User`? | |
type alias User = | |
{ id : String | |
, number : String | |
, group : String | |
, first : String | |
, last : String | |
, address1 : String | |
, address2 : String | |
, postcode : String | |
, city : String | |
, country : String | |
} | |
-- Partial function application to the rescue! We can "continue" | |
-- decoding using `andThen`: | |
userDecoder : Decoder User | |
userDecoder = | |
map8 -- First, apply the first 8 arguments | |
(string "id") | |
(string "number") | |
(string "group") | |
(string "first") | |
(string "last") | |
(string "address1") | |
(string "address2") | |
(string "postcode") | |
|> andThen (\fn -> map2 fn -- Then, apply the remaining 2 | |
(string "city") | |
(string "country") | |
) | |
-- How/why does this work? | |
-- | |
-- The `User` constructor is a function that takes 10 arguments. Like any other | |
-- function, it can be partially applied, so if applied with 8 arguments, what we | |
-- get is a function which takes 2 arguments and returns a `User`: | |
fn : String -> String -> User | |
-- When `map8` is used with the `User` function, it "consumes" 8 arguments | |
-- and returns: | |
Decoder (String -> String -> User) | |
-- Obviously, this is not `Decoder User` which the type annotation promises, so | |
-- we have to turn it into that. This is where `andThen` comes in: | |
andThen : (a -> Decoder b) -> Decoder a -> Decoder b | |
-- In this specific example, it becomes: | |
andThen : ((String -> String -> User) -> Decoder User) -> Decoder (String -> String -> User) -> Decoder User | |
-- That is, `fn` is `String -> String -> User`, so we just have to `map2` it with the | |
-- remaining 2 arguments to produce a `Decoder User`: | |
\fn -> map2 fn (string "city") (string "country") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment