Elm all the things!

Mateusz Pokora

https://github.com/elm-community/elm-webpack-starter/blob/master/src/static/img/elm.jpg

Czym jest Elm?

  • Język programowania kompilowany do JS
  • W pełni funkcyjny i statycznie typowany
  • Tworzony od 2012 (pierwszy stable release 2016)
  • Przeznaczony do tworzenia GUI

Elm - podstawy

Funkcje

  • Explicit return - Ostatnia wartość w funkcji jest zwracana
  • Pure functions - Funkcje zawsze zwracają to samo przy takich samych argumentach. Funkcje nie mają side effectów

Funkcje

// Deklaracja funkcji

sayHello : String -> String
sayHello name = 
    "Hello " ++ name ++ "!"

add : Int -> Int -> Int
add a b =
    a + b

add5 : Int -> Int
add5 a =
    add 5 a

Listy

names = [ "Alice", "Bob", "Chuck" ]
  • Zbiór wartości o tym samym typie
  • Wartości mogą się powtarzać

Record

point2D =
  { x = 0
  , y = 0
  }

point3D =
  { x = 3
  , y = 4
  , z = 12
  }

Tuple

// name, points, isAdmin
("Mateusz", 20, True)



// host, port
("192.168.0.1", 3000)
  • Zbiór kilku wartości
  • Przydatny przy zwracaniu kilku wartości z funkcji

Immutability

var user = {
    name: "Mateusz",
    age: 21
};

user.age = 100;


-----------------------------
user : User 
user = {
    name = "Mateusz"
    , age = 21
}

updatedUser = { user | age = 100 }

Nie modyfikujemy istniejących struktur. Zamiast tego tworzymy nowe aktualizując pola.

Immutability

var user = {
    name: "Mateusz",
    age: 21
};

user.age = 100;


-----------------------------
user : User 
user = {
    name = "Mateusz"
    , age = 21
}

updatedUser = { user | age = 100 }

Nie modyfikujemy istniejących struktur. Zamiast tego tworzymy nowe aktualizując pola.

Pattern matching

skipHead : List String -> List String
skipHead items =
    case items of
        [] -> 
            items
        
        head :: tail ->
            tail
            
const skipHead = (items) => {
    if (items.length === 0) {
        return items;
    } else {
        return items.slice(0, 1);
    }
}

pipe, compose

  • Ułatwia łączenie wywołań wielu funkcji
  • Przekazujemy wynik funkcji X do funkcji Y
// Javascript

displayName(
    String.reverse(
        String.capitalize("mateusz")
    )
)

-------------------------------

var capitalName = String.capitalize("mateusz")
var reversedCapitalName = String.reverse(capitalName)

displayName(reversedCapitalName)

pipe, compose

// Right pipe

"mateusz" |> String.capitalize |> String.reverse |> displayName // "zsuetaM"





// Right compose

reverseCapitalize = String.capitalize >> String.reverse >> displayName

reverseCapitalize "mateusz" // "zsuetaM"

Maybe

description : Maybe String
description =
    Just "I'm description"

description =
    Nothing

            
case description of
    Just body ->
        String.reverse body
    
    Nothing ->
        ""

Moduł

module UserForm exposing (Model, title, formNumber)

type alias Model = {
    email : String
    , name : String
    , age : String 
}

title : String
title =
    "UserForm"

formNumber : Int
formNumber =
    15


Co daje nam elm?

  • Pełne środowisko do tworzenia aplikacji webowej
  • Bezpieczeństwo w postaci silnego systemu typów i pomocnego kompilatora
  • Bardzo dobry developer experience :D

Środowisko

  • React
  • Redux
  • Immutable.js
  • Flow/Typescript
  • Webpack
  • Babel

vs.

  • Elm
  • Html.program

System typów

  • NoRedInk - 20 000 linii kodu i 0 runtime exception
  • Bezpieczny refactor
  • Mniej unit testów
  • "Jak się skompiluje to działa"

System typów

System typów

printName : String -> String
printName name = 
    "My name is :" ++ name


printName 1


///////

Function `printName` is expecting the argument to be:

    String

But it is:

    number

type alias - własne typy

type alias FormState = {
    nameInput : String
    , emailInput : String
    , ageInput : Int
    , birthDate : Date
}

Union Types

type Visibility = All | Active | Completed


type Form = 
    NotTouched 
    | Editing 
    | Submitting 
    | SubmitSuccess 
    | SubmitError

Union Types

tableView : Visibility -> Html Msg
tableView visibility = 
    div [] [
        case visibility of
            All ->
                text "Wszystkie"

            Active ->
                text "Aktywne"

            Completed ->
                text "Zakończone"
            
    ]


tableView All
const tableView = (visibility) => {
    switch(visibility) {
        case "All":
            return "Wszystkie";
        case "Active":
            return "Aktywne";
        case "Completed":
            return "Zakończone";
    }
}






tableView("All")

Obsługa wszystkich możliwości

type Visibility = 
    All 
    | Active 
    | Completed


tableView : Visibility -> Html Msg
tableView visibility = 
    div [] [
        case visibility of
            All ->
                text "Wszystkie"

            Active ->
                text "Aktywne"
            
    ]

This `case` does not have branches for all possibilities.

90|>          case visibility of
91|>            All ->
92|>                text "Wszystkie"
93|>
94|>            Active ->
95|>                text "Aktywne"

You need to account for the following values:

    Main.Completed

Add a branch to cover this pattern!
type RemoteData e a
    = NotAsked
    | Loading
    | Failure e
    | Success a

Obsługa wszystkich możliwości

Elm architecture

Text

https://elmbridge.github.io/curriculum/images/elm-architecture-1.jpeg

Html.program

Html.program
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

Funkcja opisująca jak wygląda nasza aplikacja

Model

type alias Model = {
    nameInput : String
    , emailInput : String
    , termsAndConditionsInput : Bool
    , formValid : Bool
    , modalOpen : Bool
}
  • Struktura danych i aktualny stan aplikacji
  • Aplikacja posiada jeden obiekt do reprezentacji stanu

View

view : Model -> Html Msg
view model =
    div [] [
        input [
            value model.emailInput
            , onClick UpdateEmail
        ] 
        []
    ]
  • Reprezentacja wizualna naszego modelu
  • Produkuje Msg

Update

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of 
        UpdateEmail email ->
            ({ model | email = email}, Cmd.none )
        SubmitForm ->
            // komunikacja z serwerem
  • Aktualizuje model na podstawie aktualnego stanu i Msg
  • Wywołuje side effecty (komunikacja z API)

Time travel debugging

  • Podgląd stanu aplikacji w dowolnym momencie jej działania
  • Cofnięcie aplikacji do stanu z przeszłości

Eliminacja niemożliwego stanu

Utworzenie struktury modelu w taki sposób aby wykluczyć stany które wzajemnie się wykluczają

Eliminacja niemożliwego stanu

const emailField = {
    value: "",
    errorMessage: "",
    valid: true,
    touched: false,
}

Eliminacja niemożliwego stanu

const emailField = {
    value: "email@gmail.com",
    errorMessage: "email is invalid",
    valid: false,
    touched: false,
}

Eliminacja niemożliwego stanu

type EmailField = 
    NotTouched
    | Valid String
    | Invalid String String    

Eliminacja niemożliwego stanu

type EmailField = 
    NotTouched
    | Valid String
    | Invalid String String    

Jak zacząć?

// 1. Instalacja globalna przy pomocy npm

npm install -g elm

// 2. Stworzenie początkowego projektu

// A. create-elm-app

npm install create-elm-app -g
create-elm-app my-elm-project
cd my-app/
elm-app start

// B. git clone

git clone https://github.com/moarwick/elm-webpack-starter my-elm-project
cd my-elm-project
npm install
npm start

Elm all the things

By vrael560

Elm all the things

  • 486