Skip to content

Instantly share code, notes, and snippets.

@toretore
Last active November 4, 2017 09:53
Show Gist options
  • Save toretore/10965b07bd17d8e9f7469d269e2a3e69 to your computer and use it in GitHub Desktop.
Save toretore/10965b07bd17d8e9f7469d269e2a3e69 to your computer and use it in GitHub Desktop.
--------------------------------------------------------------------------------
-- 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