Rendi affidabile il tuo front-end web con Elm
Pubblicato: 2022-03-11Quante volte hai provato a eseguire il debug del tuo front-end web e ti sei trovato invischiato in un codice che si occupava di complesse catene di eventi?
Hai mai provato a refactoring del codice per un'interfaccia utente che si occupa di molti componenti creati con jQuery, Backbone.js o altri framework JavaScript popolari?
Una delle cose più dolorose di questi scenari è cercare di seguire le molteplici sequenze indeterminate di eventi e anticipare e correggere tutti questi comportamenti. Semplicemente un incubo!
Ho sempre cercato modi per sfuggire a questo aspetto infernale dello sviluppo del front-end web. Backbone.js ha funzionato bene per me in questo senso dando ai front-end web la struttura che mancava da molto tempo. Ma data la verbosità necessaria per fare alcune delle cose più banali, non si è rivelato molto meglio.
Poi ho incontrato Elm.
Elm è un linguaggio funzionale tipizzato staticamente basato sul linguaggio di programmazione Haskell, ma con una specifica più semplice. Il compilatore (costruito anche usando Haskell) analizza il codice Elm e lo compila in JavaScript.
Elm è stato originariamente creato per lo sviluppo front-end, ma gli ingegneri del software hanno trovato il modo di usarlo anche per la programmazione lato server.
Questo articolo fornisce una panoramica di come Elm può cambiare il modo in cui pensiamo allo sviluppo del front-end web e un'introduzione alle basi di questo linguaggio di programmazione funzionale. In questo tutorial, svilupperemo una semplice applicazione simile a un carrello della spesa utilizzando Elm.
Perché Olmo?
Elm promette molti vantaggi, molti dei quali sono estremamente utili per ottenere un'architettura front-end web pulita. Elm offre migliori vantaggi in termini di prestazioni di rendering HTML rispetto ad altri framework popolari (anche React.js). Inoltre, Elm consente agli sviluppatori di scrivere codice, che in pratica non produce la maggior parte delle eccezioni di runtime che affliggono linguaggi tipizzati dinamicamente come JavaScript.
Il compilatore deduce automaticamente i tipi ed emette errori amichevoli, rendendo lo sviluppatore consapevole di qualsiasi potenziale problema prima del runtime.
NoRedInk ha 36.000 linee di Elm e, dopo oltre un anno di produzione, non ha ancora prodotto una singola eccezione di runtime. [Fonte]
Non è necessario convertire l'intera applicazione JavaScript esistente solo per poter provare Elm. Grazie alla sua superba interoperabilità con JavaScript, puoi anche prendere solo una piccola parte della tua applicazione esistente e riscriverla in Elm.
Elm ha anche un'eccellente documentazione che non solo ti fornisce una descrizione completa di ciò che ha da offrire, ma ti fornisce anche una guida adeguata alla creazione di un front-end web seguendo The Elm Architecture, qualcosa che è ottimo per la modularità, il riutilizzo del codice e il test .
Facciamo un semplice carrello
Iniziamo con un brevissimo frammento di codice Elm:
import List exposing (..) cart = [] item product quantity = { product = product, qty = quantity } product name price = { name = name, price = price } add cart product = if isEmpty (filter (\item -> item.product == product) cart) then append cart [item product 1] else cart subtotal cart = -- we want to calculate cart subtotal sum (map (\item -> item.product.price * toFloat item.qty) cart)
Qualsiasi testo preceduto da
--
è un commento in Elm.
Qui definiamo un carrello come un elenco di articoli, dove ogni articolo è un record con due valori (il prodotto a cui corrisponde e la quantità). Ogni prodotto è un record con un nome e un prezzo.
L'aggiunta di un prodotto al carrello comporta la verifica se l'articolo è già presente nel carrello.
Se lo fa, non facciamo nulla; in caso contrario, aggiungiamo il prodotto al carrello come nuovo articolo. Verifichiamo se il prodotto esiste già nel carrello filtrando l'elenco, abbinando ogni articolo al prodotto e verificando se l'elenco filtrato risultante è vuoto.
Per calcolare il totale parziale, eseguiamo un'iterazione sugli articoli nel carrello, trovando la quantità e il prezzo del prodotto corrispondenti e sommando il tutto.
Questo è minimalista come un carrello e le relative funzioni possono ottenere. Inizieremo con questo codice e lo miglioreremo passo dopo passo rendendolo un componente web completo o un programma nei termini di Elm.
Iniziamo aggiungendo tipi ai vari identificatori nel nostro programma. Elm è in grado di dedurre i tipi da solo, ma per ottenere il massimo da Elm e dal suo compilatore, si consiglia di indicare esplicitamente i tipi.
module Cart1 exposing ( Cart, Item, Product , add, subtotal , itemSubtotal ) -- This is module and its API definition {-| We build an easy shopping cart. @docs Cart, Item, Product, add, subtotal, itemSubtotal -} import List exposing (..) -- we need list manipulation functions {-| Cart is a list of items. -} type alias Cart = List Item {-| Item is a record of product and quantity. -} type alias Item = { product : Product, qty : Int } {-| Product is a record with name and price -} type alias Product = { name : String, price : Float } {-| We want to add stuff to a cart. This is a function definition, it takes a cart, a product to add and returns new cart -} add : Cart -> Product -> Cart {-| This is an implementation of the 'add' function. Just append product item to the cart if there is no such product in the cart listed. Do nothing if the product exists. -} add cart product = if isEmpty (filter (\item -> item.product == product) cart) then append cart [Item product 1] else cart {-| I need to calculate cart subtotal. The function takes a cart and returns float. -} subtotal : Cart -> Float {-| It's easy -- just sum subtotal of all items. -} subtotal cart = sum (map itemSubtotal cart) {-| Item subtotal takes item and return the subtotal float. -} itemSubtotal : Item -> Float {-| Subtotal is based on product's price and quantity. -} itemSubtotal item = item.product.price * toFloat item.qty
Con le annotazioni di tipo, il compilatore ora può rilevare problemi che altrimenti avrebbero comportato eccezioni di runtime.
Tuttavia, Elm non si ferma qui. L'architettura Elm guida gli sviluppatori attraverso un modello semplice per strutturare i loro front-end web, e lo fa attraverso concetti che la maggior parte degli sviluppatori conosce già:
- Modello: i modelli mantengono lo stato del programma.
- View: View è una rappresentazione visiva dello stato.
- Aggiornamento: l'aggiornamento è un modo per cambiare lo stato.
Se pensi alla parte del tuo codice che si occupa degli aggiornamenti come controller, allora hai qualcosa di molto simile al buon vecchio paradigma Model-View-Controller (MVC).
Poiché Elm è un puro linguaggio di programmazione funzionale, tutti i dati sono immutabili, il che significa che il modello non può essere modificato. Possiamo invece creare un nuovo modello basato sul precedente, cosa che facciamo tramite funzioni di aggiornamento.
Perché è così fantastico?
Con dati immutabili, le funzioni non possono più avere effetti collaterali. Questo apre un mondo di possibilità, incluso il debugger che viaggia nel tempo di Elm, di cui parleremo a breve.
Le viste vengono visualizzate ogni volta che una modifica nel modello richiede una modifica nella vista e avremo sempre lo stesso risultato per gli stessi dati nel modello, più o meno allo stesso modo in cui una funzione pura restituisce sempre lo stesso risultato per il stessi argomenti di input.
Inizia con la funzione principale
Andiamo avanti e implementiamo la visualizzazione HTML per la nostra applicazione carrello.
Se hai familiarità con React, questo è qualcosa che sicuramente apprezzerai: Elm ha un pacchetto solo per definire gli elementi HTML. Ciò ti consente di implementare la tua vista utilizzando Elm, senza dover fare affidamento su linguaggi di modelli esterni.
I wrapper per gli elementi HTML sono disponibili nel pacchetto Html
:
import Html exposing (Html, button, table, caption, thead, tbody, tfoot, tr, td, th, text, section, p, h1)
Tutti i programmi Elm iniziano eseguendo la funzione principale:
type alias Stock = List Product type alias Model = { cart : Cart, stock : Stock } main = Html.beginnerProgram { model = Model [] [ Product "Bicycle" 100.50 , Product "Rocket" 15.36 , Product "Biscuit" 21.15 ] , view = view , update = update }
Qui, la funzione principale inizializza un programma Elm con alcuni modelli, una vista e una funzione di aggiornamento. Abbiamo definito alcuni tipi di prodotti e i loro prezzi. Per semplicità, assumiamo di avere un numero illimitato di prodotti.
Una semplice funzione di aggiornamento
La funzione di aggiornamento è dove la nostra applicazione prende vita.
Prende un messaggio e aggiorna lo stato in base al contenuto del messaggio. La definiamo come una funzione che prende due parametri (un messaggio e il modello corrente) e restituisce un nuovo modello:
type Msg = Add Product update : Msg -> Model -> Model update msg model = case msg of Add product -> { model | cart = add model.cart product }
Per ora, stiamo gestendo un singolo caso quando il messaggio è Add product
, dove chiamiamo il metodo di add
sul cart
con il product
.
La funzione di aggiornamento aumenterà con l'aumentare della complessità del programma Elm.
Implementazione della funzione di visualizzazione
Successivamente, definiamo la vista per il nostro carrello.
La vista è una funzione che traduce il modello nella sua rappresentazione HTML. Tuttavia, non è solo HTML statico. Il generatore HTML è in grado di inviare messaggi all'applicazione in base a varie interazioni ed eventi dell'utente.
view : Model -> Html Msg view model = section [style [("margin", "10px")]] [ stockView model.stock , cartView model.cart ] stockView : Stock -> Html Msg stockView stock = table [] [ caption [] [ h1 [] [ text "Stock" ] ] , thead [] [ tr [] [ th [align "left", width 100] [ text "Name" ] , th [align "right", width 100] [ text "Price" ] , th [width 100] [] ] ] , tbody [] (map stockProductView stock) ] stockProductView : Product -> Html Msg stockProductView product = tr [] [ td [] [ text product.name ] , td [align "right"] [ text ("\t$" ++ toString product.price) ] , td [] [ button [ onClick (Add product) ] [ text "Add to Cart" ] ] ]
Il pacchetto Html
fornisce wrapper per tutti gli elementi comunemente usati come funzioni con nomi familiari (ad es. la section
della funzione genera un elemento <section>
).
La funzione style
, parte del pacchetto Html.Attributes
, genera un oggetto che può essere passato alla funzione di section
per impostare l'attributo style sull'elemento risultante.
È meglio dividere la vista in funzioni separate per una migliore riutilizzabilità.
Per semplificare le cose, abbiamo incorporato CSS e alcuni attributi di layout direttamente nel nostro codice di visualizzazione. Tuttavia, esistono librerie che semplificano il processo di modellazione degli elementi HTML dal codice Elm.
Notare il button
vicino alla fine dello snippet e come abbiamo associato il messaggio Add product
all'evento clic del pulsante.
Elm si occupa di generare tutto il codice necessario per associare una funzione di callback all'evento effettivo e di generare e chiamare la funzione di aggiornamento con i relativi parametri.
Infine, dobbiamo implementare l'ultimo bit della nostra visione:
cartView : Cart -> Html Msg cartView cart = if isEmpty cart then p [] [ text "Cart is empty" ] else table [] [ caption [] [ h1 [] [ text "Cart" ]] , thead [] [ tr [] [ th [ align "left", width 100 ] [ text "Name" ] , th [ align "right", width 100 ] [ text "Price" ] , th [ align "center", width 50 ] [ text "Qty" ] , th [ align "right", width 100 ] [ text "Subtotal" ] ] ] , tbody [] (map cartProductView cart) , tfoot [] [ tr [style [("font-weight", "bold")]] [ td [ align "right", colspan 4 ] [ text ("$" ++ toString (subtotal cart)) ] ] ] ] cartProductView : Item -> Html Msg cartProductView item = tr [] [ td [] [ text item.product.name ] , td [ align "right" ] [ text ("$" ++ toString item.product.price) ] , td [ align "center" ] [ text (toString item.qty) ] , td [ align "right" ] [ text ("$" ++ toString (itemSubtotal item)) ] ]
Qui abbiamo definito l'altra parte della nostra vista dove renderizziamo il contenuto del nostro carrello. Sebbene la funzione di visualizzazione non emetta alcun messaggio, deve comunque disporre del tipo restituito Html Msg
per qualificarsi come visualizzazione.
La visualizzazione non solo elenca il contenuto del carrello, ma calcola e rende anche il totale parziale in base al contenuto del carrello.
Puoi trovare il codice completo per questo programma Elm qui.
Se dovessi eseguire il programma Elm ora, vedresti qualcosa del genere:
Come funziona tutto?
Il nostro programma inizia con uno stato abbastanza vuoto dalla funzione main
: un carrello vuoto con alcuni prodotti codificati.
Ogni volta che si fa clic sul pulsante "Aggiungi al carrello", viene inviato un messaggio alla funzione di aggiornamento, che quindi aggiorna il carrello di conseguenza e crea un nuovo modello. Ogni volta che il modello viene aggiornato, le funzioni di visualizzazione vengono invocate da Elm per rigenerare l'albero HTML.

Poiché Elm utilizza un approccio Virtual DOM, simile a quello di React, le modifiche all'interfaccia utente vengono eseguite solo quando necessario, garantendo prestazioni rapide.
Non solo un controllo del tipo
Elm è tipizzato staticamente, ma il compilatore può controllare molto di più dei semplici tipi.
Apportiamo una modifica al nostro tipo Msg
e vediamo come reagisce il compilatore:
type Msg = Add Product | ChangeQty Product String
Abbiamo definito un altro tipo di messaggio, qualcosa che modificherebbe la quantità di un prodotto nel carrello. Tuttavia, il tentativo di eseguire nuovamente il programma senza gestire questo messaggio nella funzione di aggiornamento genererà il seguente errore:
Verso un carrello più funzionale
Si noti che nella sezione precedente abbiamo utilizzato una stringa come tipo per il valore della quantità. Questo perché il valore proverrà da un elemento <input>
che sarà di tipo string.
Aggiungiamo una nuova funzione changeQty
al modulo Cart
. È sempre meglio mantenere l'implementazione all'interno del modulo per poterla modificare in seguito, se necessario, senza modificare l'API del modulo.
{-| Change quantity of the product in the cart. Look at the result of the function. It uses Result type. The Result type has two parameters: for bad and for good result. So the result will be Error "msg" or a Cart with updated product quantity. -} changeQty : Cart -> Product -> Int -> Result String Cart {-| If the quantity parameter is zero the product will be removed completely from the cart. If the quantity parameter is greater then zero the quantity of the product will be updated. Otherwise (qty < 0) the error will be returned. -} changeQty cart product qty = if qty == 0 then Ok (filter (\item -> item.product /= product) cart) else if qty > 0 then Result.Ok (map (\item -> if item.product == product then { item | qty = qty } else item) cart) else Result.Err ("Wrong negative quantity used: " ++ (toString qty))
Non dovremmo fare ipotesi su come verrà utilizzata la funzione. Possiamo essere certi che il parametro qty
conterrà un Int
ma il valore può essere qualsiasi cosa. Pertanto controlliamo il valore e segnaliamo un errore quando non è valido.
Aggiorniamo anche la nostra funzione update
di conseguenza:
update msg model = case msg of Add product -> { model | cart = add model.cart product } ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> { model | cart = cart } Err msg -> model -- do nothing, the wrong input Err msg -> model -- do nothing, the wrong quantity
Convertiamo il parametro della quantity
di stringa dal messaggio in un numero prima di usarlo. Nel caso in cui la stringa contenga un numero non valido, lo segnaliamo come errore.
Qui, manteniamo invariato il modello quando si verifica un errore. In alternativa, potremmo semplicemente aggiornare il modello in modo da segnalare l'errore come messaggio nella vista che l'utente può vedere:
type alias Model = { cart : Cart, stock : Stock, error : Maybe String } main = Html.beginnerProgram { model = Model [] -- empty cart [ Product "Bicycle" 100.50 -- stock , Product "Rocket" 15.36 , Product "Bisquit" 21.15 ] Nothing -- error (no error at beginning) , view = view , update = update } update msg model = case msg of Add product -> { model | cart = add model.cart product } ChangeQty product str -> case toInt str of Ok qty -> case changeQty model.cart product qty of Ok cart -> { model | cart = cart, error = Nothing } Err msg -> { model | error = Just msg } Err msg -> { model | error = Just msg }
Usiamo il tipo Maybe String
per l'attributo di errore nel nostro modello. Forse è un altro tipo che può contenere Nothing
o un valore di un tipo specifico.
Dopo aver aggiornato la funzione di visualizzazione come segue:
view model = section [style [("margin", "10px")]] [ stockView model.stock , cartView model.cart , errorView model.error ] errorView : Maybe String -> Html Msg errorView error = case error of Just msg -> p [style [("color", "red")]] [ text msg ] Nothing -> p [] []
Dovresti vedere questo:
Il tentativo di inserire un valore non numerico (ad es. "1a") risulterebbe in un messaggio di errore come mostrato nella schermata sopra.
Il mondo dei pacchetti
Elm ha un proprio repository di pacchetti open source. Con il gestore di pacchetti per Elm, diventa un gioco da ragazzi sfruttare questo pool di pacchetti. Sebbene la dimensione del repository non sia paragonabile ad altri linguaggi di programmazione maturi come Python o PHP, la comunità di Elm sta lavorando duramente per implementare più pacchetti ogni giorno.
Notare come le cifre decimali nei prezzi resi dal nostro punto di vista sono incoerenti?
Sostituiamo il nostro uso ingenuo di toString
con qualcosa di meglio dal repository: numeral-elm.
cartProductView item = tr [] [ td [] [ text item.product.name ] , td [ align "right" ] [ text (formatPrice item.product.price) ] , td [ align "center" ] [ input [ value (toString item.qty) , onInput (ChangeQty item.product) , size 3 --, type' "number" ] [] ] , td [ align "right" ] [ text (formatPrice (itemSubtotal item)) ] ] formatPrice : Float -> String formatPrice price = format "$0,0.00" price
Stiamo usando la funzione di format
del pacchetto Numeral qui. Ciò formatterebbe i numeri in un modo in cui normalmente formattiamo le valute:
100.5 -> $100.50 15.36 -> $15.36 21.15 -> $21.15
Generazione automatica della documentazione
Quando si pubblica un pacchetto nel repository Elm, la documentazione viene generata automaticamente in base ai commenti nel codice. Puoi vederlo in azione controllando la documentazione per il nostro modulo Carrello qui. Tutti questi sono stati generati dai commenti visti in questo file: Cart.elm.
Un vero debugger per front-end
I problemi più evidenti vengono rilevati e segnalati dal compilatore stesso. Tuttavia, nessuna applicazione è al sicuro da errori logici.
Poiché tutti i dati in Elm sono immutabili e tutto avviene tramite messaggi passati alla funzione di aggiornamento, l'intero flusso di un programma Elm può essere rappresentato come una serie di modifiche del modello. Per il debugger, Elm è proprio come un gioco di strategia a turni. Ciò consente al debugger di eseguire alcune prodezze davvero sorprendenti, come viaggiare nel tempo. Può spostarsi avanti e indietro nel flusso di un programma saltando tra le varie modifiche del modello avvenute durante la vita di un programma.
Puoi saperne di più sul debugger qui.
Interagire con un Back-end
Quindi, dici, abbiamo costruito un bel giocattolo, ma Elm può essere usato per qualcosa di serio? Assolutamente.
Colleghiamo il front-end del nostro carrello con un back-end asincrono. Per renderlo interessante, implementeremo qualcosa di speciale. Diciamo che vogliamo ispezionare tutti i carrelli e il loro contenuto in tempo reale. Nella vita reale, potremmo utilizzare questo approccio per portare alcune capacità di marketing/vendita extra nel nostro negozio online o mercato, o dare alcuni suggerimenti all'utente, o stimare le risorse di scorta richieste e molto altro.
Quindi, memorizziamo il carrello sul lato client e informiamo anche il server di ciascun carrello in tempo reale.
Per semplificare le cose, implementeremo il nostro back-end usando Python. Puoi trovare il codice completo per il back-end qui.
È un semplice server web che utilizza un WebSocket e tiene traccia del contenuto del carrello in memoria. Per semplificare le cose, renderemo il carrello di tutti gli altri sulla stessa pagina. Questo può essere facilmente implementato in una pagina separata o anche come programma Elm separato. Per ora, ogni utente potrà vedere il riepilogo dei carrelli degli altri utenti.
Con il back-end attivo, ora dovremo aggiornare la nostra app Elm per inviare e ricevere gli aggiornamenti del carrello al server. Useremo JSON per codificare i nostri payload, per i quali Elm ha un eccellente supporto.
CartEncoder.elm
Implementeremo un codificatore per convertire il nostro modello di dati Elm in una rappresentazione di stringa JSON. Per questo, dobbiamo usare la libreria Json.Encode.
module CartEncoder exposing (cart) import Cart2 exposing (Cart, Item, Product) import List exposing (map) import Json.Encode exposing (..) product : Product -> Value product product = object [ ("name", string product.name) , ("price", float product.price) ] item : Item -> Value item item = object [ ("product", product item.product) , ("qty", int item.qty) ] cart : Cart -> Value cart cart = list (map item cart)
La libreria fornisce alcune funzioni (come string
, int
, float
, object
, ecc.) che prendono oggetti Elm e li trasformano in stringhe codificate JSON.
CartDecoder.elm
L'implementazione del decoder è un po' più complicata poiché tutti i dati Elm hanno tipi e dobbiamo definire quale valore JSON deve essere convertito in quale tipo:
module CartDecoder exposing (cart) import Cart2 exposing (Cart, Item, Product) -- decoding for Cart import Json.Decode exposing (..) -- will decode cart from string cart : Decoder (Cart) cart = list item -- decoder for cart is a list of items item : Decoder (Item) item = object2 Item -- decoder for item is an object with two properties: ("product" := product) -- 1) "product" of product ("qty" := int) -- 2) "qty" of int product : Decoder (Product) product = object2 Product -- decoder for product also an object with two properties: ("name" := string) -- 1) "name" ("price" := float) -- 2) "price"
Applicazione Elm aggiornata
Poiché il codice Elm finale è un po' più lungo, puoi trovarlo qui. Di seguito è riportato un riepilogo delle modifiche apportate all'applicazione front-end:
Abbiamo racchiuso la nostra funzione di update
originale con una funzione che invia le modifiche ai contenuti del carrello al back-end ogni volta che il carrello viene aggiornato:
updateOnServer msg model = let (newModel, have_to_send) = update msg model in case have_to_send of True -> -- send updated cart to server (!) newModel [ WebSocket.send server (encode 0 (CartEncoder.cart newModel.cart)) ] False -> -- do nothing (newModel, Cmd.none)
Abbiamo anche aggiunto un tipo di messaggio aggiuntivo di ConsumerCarts String
per ricevere aggiornamenti dal server e aggiornare di conseguenza il modello locale.
La vista è stata aggiornata per rendere il riepilogo dei carrelli degli altri utilizzando la funzione consumersCartsView
.
È stata stabilita una connessione WebSocket per iscriversi al back-end per ascoltare le modifiche ai carrelli degli altri.
subscriptions : Model -> Sub Msg subscriptions model = WebSocket.listen server ConsumerCarts server = "ws://127.0.0.1:8765"
Abbiamo anche aggiornato la nostra funzione principale. Ora utilizziamo Html.program
con parametri di init
e subscriptions
aggiuntivi. init
specifica il modello iniziale del programma e la subscription
specifica un elenco di sottoscrizioni.
Un abbonamento è un modo per dire a Elm di ascoltare le modifiche su canali specifici e inoltrare quei messaggi alla funzione di update
.
main = Html.program { init = init , view = view , update = updateOnServer , subscriptions = subscriptions } init = ( Model [] -- empty cart [ Product "Bicycle" 100.50 -- stock , Product "Rocket" 15.36 , Product "Bisquit" 21.15 ] Nothing -- error (no error at beginning) [] -- consumer carts list is empty , Cmd.none)
Infine abbiamo gestito il modo in cui decodifichiamo il messaggio ConsumerCarts che riceviamo dal server. Ciò garantisce che i dati che riceviamo da una fonte esterna non interrompano l'applicazione.
ConsumerCarts message -> case decodeString (Json.Decode.list CartDecoder.cart) message of Ok carts -> ({ model | consumer_carts = carts }, False) Err msg -> ({ model | error = Just msg, consumer_carts = [] }, False)
Mantieni i tuoi front-end sani
L'olmo è diverso. Richiede allo sviluppatore di pensare in modo diverso.
Chiunque provenga dal regno di JavaScript e linguaggi simili si ritroverà a cercare di imparare il modo di fare le cose di Elm.
In definitiva, però, Elm offre qualcosa che altri framework, anche i più popolari, spesso faticano a fare. Vale a dire, fornisce un mezzo per creare robuste applicazioni front-end senza rimanere impigliato in un codice enormemente dettagliato.
Elm elimina anche molte delle difficoltà che JavaScript pone combinando un compilatore intelligente con un potente debugger.
Elm è ciò che gli sviluppatori front-end desideravano da così tanto tempo. Ora che l'hai visto in azione, provalo tu stesso e cogli i benefici costruendo il tuo prossimo progetto web in Elm.