Bezbłędny frontend z Elm

Andrzej Kopeć

Czym w ogóle jest Elm? I czy ktoś go w ogóle potrzebuje?

A co to w ogóle znaczy, że funkcyjny?

Niemutowalność danych

Bo mutacja danych to efekt uboczny...

...a efektów ubocznych brak

Funkcja może tylko obliczyć nowy wynik i go zwrócić

Na szczęście funkcja to też wartość

System typów Damasa–Hindleya–Milnera

Make invalid states impossible to represent

Świetny kompilator

ale miał być do frontendu...

  • The Elm Architecture
  • Wbudowana biblioteka do Virtual DOM
  • Asynchroniczność
  • Porty i flagi

The Elm Architeture - wygląda znajomo?

import Html exposing (..)

main = Html.beginnerProgram { model = 0, update = update, view = view }

-- MODEL
type alias Model = Int

-- Message
type Msg = MakeCookie

-- UPDATE
update : Msg -> Model -> Model
update msg model =
  case msg of
    MakeCookie -> model + 1

-- VIEW
view : Model -> Html Msg
view model = img [src "img/cookie.jpg", class "cookie", onClick MakeCookie] []

Jako dodatek mamy jeszcze subskrybcje...

import Html exposing (..)

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

init : (Model, Cmd Msg)
init = (0, Cmd.none)

subscriptions : Model -> Sub Msg
subscriptions model =
    Time.every second MakeCookie

-- ..

... i komendy

type Msg = RemoveNotification notification | RestOfTypes

update : Msg -> Model -> (Model, Cmd msg)
update msg model =
    case msg of
        AddNotification notification ->
            (
                { model
                | notifications = model.notifications
                    |> List.reverse
                    |> (::) notification
                    |> List.reverse
                }
            , Notification.remove RemoveNotification notification
            )
-- ..

Efekt? Pełne wsparcie dla asynchronicznych i powtarzających się akcji

Wbudowane w język

Zadania (czyli Task)

To takie Promise'y, ale zintegrowane z resztą architektury

module Notifications exposing (..)

add : (Notification -> msg) -> String -> Cmd msg
add tagger notification =
    Task.perform tagger (Time.now
        |> map (\time -> round (Time.inMilliseconds time))
        |> map (\seed -> Random.step (Random.int minInt maxInt) (initialSeed seed))
        |> map Tuple.first
        |> map (\id -> Notification notification id))

remove : (Notification -> msg) -> Notification -> Cmd msg
remove tagger notification =
    Task.perform tagger (Process.sleep (5 * Time.second)
        |> Task.andThen (always <| Task.succeed notification))

A jak się dogadać z JS?

Portami, czyli przerzucamy wartości przez wielki mur - najpierw do JS

port module User exposing (..)

import Json.Decode

port getUserId : String -> Cmd msg

port userId : (String -> msg) -> Sub msg

... po czym JS rzuca do nas odpowiedzi

import * as Elm from './Main';


const app = Elm.Main.embed(document.getElementById('app'));

app.ports.getUserId.subscribe(() => {
    getUserId()
        .then((userId) => app.ports.userId.send(userId));
});

Ok, coś jeszcze?

  • Biblioteka do Http
  • Biblioteki z różnymi strukturami danych
  • Biblioteki do obsługi JSON
  • Własne operatory
  • Pełne sprawdzanie typów w trakcie działania programu
  • Świetny kompilator
  • Union Types
  • Prostota

Czyli święty Graal na frontend?

Nie dla każdego

Questions time!

andrzej.kopec@outlook.com
@kapke_