Build elegant UIs the functional way

(And become a better Javascript dev in the process!)

Christopher Bloom, @illepic

Elm

Phase2 is a digital agency moving industry leading organizations forward with powerful ideas and executable strategies built on open technology. We are experts at building and designing websites, applications, and digital platforms that position our clients to engage with their target audiences in any channel, on any device, wherever they may be.

@phase2

Let's build a javascript app!

  • Package management! NPM, of course! Or Yarn!
  • View layer/virtual DOM: React! Ember! Vue!
  • Data flow and state management: Redux! VueX! MobX?
  • Immutable data structures! Immutable.js! Seamless! Mori!
  • Strong and custom types! Flow! TypeScript!
  • Dev server/hot reloading: Webpack! Parcel!
  • Transpilation + Linting: Babel and AirBnB Styleguide!
  • Auto-formatting: Prettier!

Now wire this all together! Good luck!

If that was overwhelming, you're in the right session.

PRO:

We can do anything in javascript!

Photo by Rene Bernal on Unsplash

Photo by Zoltan Tasi on Unsplash

CON:

We can do anything in javascript!

Wouldn't it be nice if there was "one way"? A safe, stable way?

Elm & Drupal

Amitai Burstein, www.gizra.com.

 

DrupalCon NOLA:

Frontend With Guarantees

bit.ly/2N0vCCM

 

DrupalCon Vienna:

Elm and Haskell, Getting Off the Island.

bit.ly/2MoLbrZ

 

No runtime errors in practice. No null. No undefined is not a function.

 

"No combination of JS libraries can ever give you this, yet it is all free and easy in Elm. Now these nice things are only possible because Elm builds upon 40+ years of work on typed functional languages."

Elm provides:

  • Package management!
  • A blazing fast virtual DOM!
  • Immutable data structures!
  • Static typing!
  • Unidirectional data architecture!
  • Purely functional paradigm!
  • Strictly enforced semver!
  • A helpful, friendly compiler
  • Code auto-formatter
  • Compiles to javascript
  • Interacts with external javascript
  • Super clean syntax!
  • Local dev server

Elm is all about functions

add num1 num2 = num1 + num2
sayWoof animal = animal ++ " says woof!"

--

add 3 3 -- 6
sayWoof "cat" -- "cat says woof!"
function add(num1, num2) {
  return num1 + num2;
}
function sayWoof(animal) {
  return `${animal} says woof!`;
}

// --

add(3, 3) // 6
sayWoof('cat') // 'cat says woof!'

(If functions are so important in JS, why all those extra characters?)

Functions are pure

mutateMe = 7
mutateOutside = mutateMe + 1

--

mutateOutside -- 8
mutateMe -- 7
-- NO MECHANISM TO CHANGE OUTSIDE VARS!
let mutateMe = 7;
function mutateOutside() {
  mutateMe++;
  return mutateMe;
}

// --

mutateOutside() // 8
mutateMe // 8
// OH NOES, SIDE EFFECT!

Elm is "declarative"

priceList =
    [1, 2, 3, 4]


makeProfit price =
    price * 1.5


markupInventory list =
    List.map makeProfit list

--

markupInventory priceList -- 1.5, 3, 4.5, 6
const priceList = [1, 2, 3, 4];

function markupInventory(list) {
  const profitList = [];
  const length = list.length;

  for (let i = 0; i < length; i++) {
    profitList.push(list[i] * 1.5);
  }

  return profitList;
}

// --

markupInventory(priceList); // [1.5, 3, 4.5, 6]

Currying by default


babyAnimals a b =
    "i love " ++ a ++ " and " ++ b

babyKoalas = babyAnimals "koalas"

--

babyKoalas "puppies" -- "i love koalas and puppies"
babyKoalas "kittens" -- "i love koalas and kittens"
babyKoalas "pandas" -- "i love koalas and pandas"

--

babyAnimals "koalas" "kittens"
-- "i love koalas and kittens"
function babyAnimals(a) {
  return function(b) {
    return `i love ${a} and ${b}`;
  }
}

const babyKoala = babyAnimals('koalas');

//

babyKoala('puppies'); // 'i love koalas and puppies'
babyKoala('kittens'); // 'i love koalas and kittens'
babyKoala('pandas'); // 'i love koalas and pandas'

//
babyAnimals('koalas')('kittens');
// 'i love koalas and kittens'

Piping

list =
    List.range 1 5

list
    |> List.map (\n -> n * 2) -- [2,4,6,8,10]
    |> List.filter (\n -> n > 6) -- [8,10]
    |> List.map (\n -> n * n) -- [64,100]
import _ from 'lodash';

const list = _.range(1, 6);

_
  .chain(list)
  .map(n => n * 2) // [2,4,6,8,10]
  .filter(n => n > 6) // [8,10]
  .map(n => n * n) // [64,100]
  .value() // [64,100]

+

Static typing: Elm

-- Values

amBald : Bool
amBald = True

catNoise : String
catNoise = "meow"

nums : List Int
nums = [1, 2, 3]

-- Functions

sayMeow : String -> String
sayMeow animal =
    animal ++ " says meow"

add : Int -> Int -> Int
add x y =
    x + y
> sayMeow 19
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm

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

3|   sayMeow 19
             ^^
Function `sayMeow` is expecting the argument to be:

    String

But it is:

    number

Records, Union types, Type aliases


type Disposition = Surly | Friendly | Ambivalent
type alias Cat = {
    name: String, 
    weight: Float, 
    disposition: Disposition 
    }

seamus : Cat
seamus =
    { name = "Seamus"
    , weight = 22.2
    , disposition = Surly
    }

--

seamus.name -- "Seamus"
seamus.weight -- 22.2
.name seamus -- "Seamus"

// Flow + Webpack + Babel config required!

/*@flow*/

type Disposition = 'Surly' | 'Friendly' | 'Ambivalent'

type Cat = {
  name: string,
  weight: number,
  disposition: Disposition,
};


const seamus: Cat = {
  name: 'Seamus',
  weight: 22.2,
  disposition: 'Surly',
};

+

+

+

Type aliases, Immutable


type Disposition = Surly | Friendly | Ambivalent
type alias Cat = {
    name: String, 
    weight: Float, 
    disposition: Disposition 
    }

seamus = Cat "Seamus" 22.2 Surly
debris = Cat "Debris" 9.1 Friendly
simone = Cat "Simone" 7.2 Ambivalent

--

piggySeamus = { seamus | weight = seamus.weight * 1.1 }

piggySeamus.weight -- 24.42 : Float
seamus.weight -- 22.2 : Float

Union self-referencing


type Disposition
    = Surly
    | Friendly
    | Ambivalent
    | Mix Disposition Disposition

-- type alias Cat = { ...

seamus = Cat "Seamus" 22.2 Surly
debris = Cat "Debris" 9.1 Friendly
simone = Cat "Simone" 7.2 (Mix Ambivalent Surly)

Maybe (null!), Case

-- Special type built in to Elm

type Maybe a -- "a" here means "anything"
    = Just a -- "Just have a user, Cat, value"
    | Nothing -- "Or I don't have anything"

--

type alias User =
  { name : String
  , age : Maybe Int
  }

sue : User
sue =
  { name = "Sue", age = Nothing }

tom : User
tom =
  { name = "Tom", age = Just 24 }
canBuyAlcohol : User -> Bool
canBuyAlcohol user =
  case user.age of
    Nothing ->
      False
    Just age ->
      age >= 21
--
canBuyAlcohol tom -- True
canBuyAlcohol sue -- False
--

More Maybe

myList : List String                      
myList = ["First", "Second"]         

-- List.head : List a -> Maybe.Maybe a

case List.head myList of                  
  Nothing ->                              
    "Empty list!"

  Just val ->                             
    val

-- List.head myList returns a Maybe, NOT value
let myList = ['First', 'Second'];

myList[0]; // "First"

let myList = [];

myList[0]; // undefined, *KABOOOOOOM*

Decoding JSON

All data must be "decoded" and typed to flow into Elm.

--
getRandomGif : String -> Cmd Msg
getRandomGif topic =
    let
        url =
            "https://api.giphy.com/v1/..." ++ topic
    in
    Http.send NewGif (Http.get url decodeGifUrl)

--
decodeGifUrl : Decode.Decoder String
decodeGifUrl =
    Decode.at [ "data", "image_url" ] Decode.string

Returns Request type

Returns (Cmd Msg)

Decoding JSON (cont'd)

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    MorePlease ->
      (model, getRandomGif model.topic)

    NewGif (Ok newUrl) ->
      (Model model.topic newUrl, Cmd.none)

    NewGif (Err _) ->
      (model, Cmd.none)

Elm chooses this if Request succedes

Elm chooses this if Request fails

How?

How?

Actually, possible because of the

 

Elm Architecture

 

(We'll get to that soon)

That "No Runtime Errors" thing

Elm is safe because there are no "null" values, types ensure consistency, and control statements force dealing with all possibilities.

Photo by Sergey Schmidt on Unsplash

Modern Frontend Tooling

  • Package management! NPM, of course! Or Yarn!
  • View layer/virtual DOM: React! Vue!
  • Data flow and state management: Redux! VueX! MobX?
  • Immutable data structures! Immutable.js!
  • Strong and custom types! Flow! TypeScript!
  • Dev server/hot reloading: Webpack! Parcel?
  • Transpilation + Linting: Babel + AirBnB Styleguide?
  • Auto-formatting: Prettier!

Elm's got it.

Elm Tooling

  • Package management! elm-package
  • View layer/virtual DOM: Core
  • Data flow and state management: Core (Elm Architecture)
  • Immutable data structures! Core
  • Strong and custom types! Core
  • Dev server/hot reloading: elm-reactor
  • Transpilation + Linting: Core
  • Auto-formatting: elm-format

Building For Production

Make a binary that includes the Elm runtime:

elm-make src/Main.elm --output=build/main.js
<script src="build/main.js"></script>
<script>
  const node = document.getElementById('main');
  const app = Elm.Main.embed(node);
</script>

Initialize with JavaScript:

Elm Architecture

Elm Runtime

Msg

update

Html

view

model

Elm Architecture: Redux?

Msg

update

Html

view

model

Elm Architecture

 When the user clicks on a button, it produces a message. That message is piped into the update function, producing a new model. We use the view function to show the new model on screen. And then we just repeat this forever!

Elm Architecture (code demo)

Elm: Build Elegant UIs the Functional Way

By Christopher Bloom

Elm: Build Elegant UIs the Functional Way

Elm is a language that compiles to javascript that provides the features of many custom javascript libraries in one. Let's take a quick tour of neat things that Elm can do.

  • 3,620