Skip to content

Instantly share code, notes, and snippets.

@toretore toretore/elm-tips.elm
Last active Nov 4, 2017

Embed
What would you like to do?
--------------------------------------------------------------------------------
-- 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
You can’t perform that action at this time.