The state of your whole application is stored in a record tree
Principle 2: State is read-only
The only way to mutate the state is to emit an action describing what happened
module Counter exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
-- MODEL
type alias Model =
Int
initialModel : Model
initialModel =
0
init : ( Model, Cmd Msg )
init =
( initialModel
, Cmd.none
)
-- UPDATE
type Msg
= Decrement
| Increment
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Decrement ->
( model - 1
, Cmd.none
)
Increment ->
( model + 1
, Cmd.none
)
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
]
Model the problem
--
type alias User =
{ avatarUrl : String
, name : String
}
viewAvatar : String -> Html a
viewAvatar url =
img [ src url ] []
--
--
type alias User =
{ avatarUrl : String
, name : String
}
viewAvatar : String -> Html a
viewAvatar url =
img [ src url ] []
main = viewAvatar "But this isn't a URL!"
Compile Error Vs Runtime Mistakes
Types 101
type Bool = False | True
type UserType = Anonymous | Pending Integer | Authenticated String
--
type alias User =
{ avatarUrl : String
, name : String
}
viewAvatar : String -> Html a
viewAvatar url =
img [ src url ] []
--
type Url = Url String
type alias User =
{ avatarUrl : Url
, name : String
}
viewAvatar : Url -> Html a
viewAvatar url =
let
(Url val) = url
in
img [ src val ] []
type Url = Url String
type alias User =
{ avatarUrl : Url
, name : String
}
viewAvatar : Url -> Html a
viewAvatar (Url val) =
img [ src val ] []
--
Maybe values
type alias User =
{ avatarUrl : Url
, name : String
}
emptyUser =
{ avatarUrl = Url ""
, name = ""
}
type alias Model =
{ activePage : Page
, user : User
}
emptyModel =
{ activePage = Login
, user = emptyUser
}
type alias Model =
{ activePage : Page
, user : Maybe User
}
emptyModel =
{ activePage = Login
, user = Nothing
}
--
type alias Model =
{ activePage : Page
, user : Maybe User
}
emptyModel =
{ activePage = Login
, user = Just { name = "Amitai"
, avatarUrl = "https://example.com/avatar"
}
}
But we can do better
{-|
* `NotAsked` - We haven't asked for the data yet.
* `Loading` - We've asked, but haven't got an answer yet.
* `Failure` - We asked, but something went wrong. Here's the error.
* `Success` - Everything worked, and here's the data.
-}
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
type alias WebData a =
RemoteData Http.Error a
type alias Model =
{ activePage : Page
, user : WebData User
}
emptyModel =
{ activePage = Login
, user = NotAsked
}
Learn how to re-think with types
type Language
= English
| Spanish
type TranslationId
= Login
| WelcomeBack { name : String }
type alias TranslationSet =
{ english : String
, spanish : String
}
translations : TranslationId -> TranslationSet
translations id =
case id of
Login ->
{ english = "Please login"
, spanish = "Por favor haga login"
}
WelcomeBack val ->
{ english = "Welcome back " ++ val.name
, spanish = "Bienvenido " ++ val.name
}
translate : Language -> TranslationId -> String
translate lang id =
case lang of
English ->
.english <| translations id
Spanish ->
.spanish <| translations id
Not everything is in Types
update : Msg -> Model -> Model
update msg model =
case msg of
Decrement ->
model - 1
Increment ->
model + 1
update : Msg -> Model -> Model
update msg model =
case msg of
Decrement ->
let
newModel =
if model < 1 then 0 else model - 1
in
newModel
Increment ->
model + 1
Buisness Logic requires Testing
Unit tests
Pure Functions & Side Effects
-- MODEL
type alias Model = Int
-- UPDATE
type Msg
= Fetch
| FetchSucceed Int
| FetchFail Http.Error
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Fetch ->
( model
, Task.perform FetchFail FetchSucceed (Http.get "https://api.example.com")
)
FetchSucceed count ->
( count, Cmd.none)
FetchFail _ ->
( model, Cmd.none )