gizra // @amitaibu

Elm

A different approach to frontend webapps

Our evaluation process

https://github.com/Gizra/elm-hedley
https://github.com/Gizra/elm-spa-example

Elm & Elm Architecture

Principle 1: Single source of truth

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 )

         

Yeoman Generator

https://github.com/Gizra/generator-elmlang
  • Gulp
  • browserSync
  • Auto-compile
  • Sass
  • Bundle and Deploy to gh-pages
gizra | We are Hiring