loganhenson
10/21/2016 - 4:09 PM

Elm - the why guide

Elm - the why guide

Elm - the why guide

Why should I waste spend my time learning yet another javascript framework/language/thing?

First I want to set the tone of this post. We are taking an objective look at the javascript landscape and taking a bet on a tool.

Stage

  • we want correct software
  • with the least amount of time invested
  • we have more than 1 person working on it

Lets define this stuff:

Correct

The program does what the stakeholders want it to do

More than 1 person

More than one person, different opinions and experience levels that need to work together effectively

To answer that question let's first check out the landscape.

Contenders

  • Javascript
  • Vue
  • Ember
  • Angular
  • Angular2
  • React
  • Elm
  • Purescript

Imperitive

mutates state

Functional

you can write code that does not mutate state

Purely Functional

you must write code that does not mutate state

Dynamically Typed

does not require you explicity define types

Strictly Typed

you must explicity define types

Blessed Style / Format

your team doesn't have to waste time on style

Here I will define my personal score card for what I want, yours will likely differ (and probably should)

  • Imperitive: 0
  • Functional: 1
  • Purely Functional: 1
  • Dynamically Typed: 0
  • Strictly Typed: 1
  • Blessed Style / Format: 1

Contenders

  • Javascript: 1+1
  • Vue: 1+1
  • Ember: 1+1
  • Angular: 1+1
  • Angular2: 1+1
  • React: 1+1
  • Elm: 1+1+1+1
  • Purescript: 1+1+1+1

Elm and Purescript (and others) fall into the same category, but I will be choosing Elm purely on the basis that it is simpler for now.

---- comparison

If you made it this far, I'll go ahead and show you the table of contents.

Table of contents

why guide (you already did this, good job!) installation syntax the elm architecture tips tricks make a thing make a thing using laravel

Installation

First, we need to install the Elm tools, more details can be found on https://guide.elm-lang.org/get_started.html but this is what you will need for this article -- The “Elm Platform” which includes all the command line tools you will need to work with Elm.

Syntax

1. No runtime errors. (Yes, this means no more undefined is not a function)

You cannot get a runtime error, because you cannot write a program that allows a state that would create one. Its as simple as that.

Here is an example of what I mean:

import Html exposing (text)

main =
  text 123

This simple program demonstrates something that the compiler just won't allow:

Detected errors in 1 module.


-- TYPE MISMATCH ---------------------------------------------------------------

The argument to function `text` is causing a mismatch.

5|   text 123
          ^^^
Function `text` is expecting the argument to be:

    String

But it is:

    number

Lets sort this out (pretty easy as the compiler gave us the answer!)

import Html exposing (text)

main =
  text "123"

or

import Html exposing (text)

main =
  text (toString 123)

As you can see, Elm forces you to be explicit, and while it may seem trivial here, it is such a great feeling for a larger app to know that if it compiles, then it will do what you explicitly instructed it to do!

2. This fancy thing called currying. I promise it isn't as scary as it sounds!

Lets say we wanted a couple functions, but they were super similar...

import Html exposing (text)

main =
  text (toString(add5(1)))
  
add a b =
  a + b
  
add5 a =
  5 + a

We can re-use functionality!

import Html exposing (text)

main =
  text (toString(add5(1)))
  
add a b =
  a + b
  
add5 =
  add 5

If you take a look above, you can see that we have preloaded or curried the add function with 5 to create add5. Imagine if it were more complex!

3. Guess what? We don't need all these parenthesis either.

Elm has an operator (borrowed from F#, inspired by Unix pipes) that you can use to make your code oh so smooth. Meet the |> operator.

import Html exposing (text)

main =
  1
    |> add5
    |> toString
    |> text
      
add a b =
  a + b
  
add5 =
  add 5

Now you can read the program just like you would a sentence!

take 1, add 5 to it, then make it a string, then make it some html text

The Elm architecture

Tips

Tricks

Make a thing

Make a thing using Laravel

To install our NPM dependencies, run npm install --save-dev laravel-elixir-elm.

Next, head into the gulpfile.js at the root of your project and replace require('laravel-elixir-vue'); with require('laravel-elixir-elm');, and add elm to your build process.

gulpfile.js

const elixir = require('laravel-elixir');

require('laravel-elixir-elm');

elixir(mix => {
    mix.sass('app.scss')
       .elm('example');
});

This package will handle using elm-make to transpile your Elm code into a single example.js file that can be served by your application (this does not need to be manually linked to, laravel-elm will embed the script in the page).

Next lets grab a composer package to help integrate it into our laravel app.

Run this from your project root

composer install tightenco/laravel-elm

And add the service provider to your application.

config/app.php

...
'providers' => [
    '...',
    Tightenco\Elm\ElmServiceProvider::class
];
...

Let's create a simple Elm program! This will create the directory and file: resources/assets/elm/example/Main.elm:

Run this from your project root

php artisan elm:create example

Time to use our basic program in laravel! First update your HomeController@index method to read:

app/Http/Controllers/HomeController.php

use Elm;
...
return view('home', [
    'example' => Elm::make('example')
]);
...

Finally, we'll want to update our example program to make an API call to fetch this data. resources/assets/elm/example/Main.elm

module Main exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.App exposing (programWithFlags)
import List
import Platform.Cmd
import Http
import Task
import Json.Decode exposing ((:=))


type alias User =
    { id : Int
    , name : String
    , email : String
    }


type alias Model =
    { name : String
    , users : List User
    }


type Msg
    = FetchFail Http.Error
    | FetchSucceed (List User)


main : Program { name : String }
main =
    programWithFlags { init = init, view = view, update = update, subscriptions = \_ -> Sub.none }


init : { name : String } -> ( Model, Cmd Msg )
init flags =
    ( { name = flags.name, users = [] }, fetchUsers )


renderUser : User -> Html a
renderUser user =
    tr []
        [ td [] [ text <| toString user.id ]
        , td [] [ text user.name ]
        , td [] [ text user.email ]
        ]


view : Model -> Html.Html a
view model =
    div [] []


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FetchSucceed users ->
            ( { model | users = users }, Cmd.none )

        FetchFail error ->
            ( model, Cmd.none )


user : Json.Decode.Decoder User
user =
    Json.Decode.object3 User
        ("id" := Json.Decode.int)
        ("name" := Json.Decode.string)
        ("email" := Json.Decode.string)


fetchUsers : Cmd Msg
fetchUsers =
    Task.perform FetchFail FetchSucceed (Http.get (Json.Decode.list user) "/api/users")