elm: give it a try

41
elm: give it a try Eugene Zharkov, JUNO

Upload: eugene-zharkov

Post on 24-Jan-2018

703 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Elm: give it a try

elm: give it a tryEugene Zharkov, JUNO

Page 2: Elm: give it a try

the best of functional programming in your browser

Page 3: Elm: give it a try

So Elm…readItem :: String -> Maybe Decl readItem x | ParseOk y <- myParseDecl x = Just $ unGADT y readItem x -- newtype | Just x <- stripPrefix "newtype " x , ParseOk (DataDecl a _ c d e f g) <- fmap unGADT $ myParseDecl $ "data " ++ x = Just $ DataDecl a NewType c d e f g readItem x -- constructors | ParseOk (GDataDecl _ _ _ _ _ _ [GadtDecl s name _ ty] _) <- myParseDecl $ "data Data where " ++ x , let f (TyBang _ (TyParen x@TyApp{})) = x f (TyBang _ x) = x f x = x

parserC warning = f [] "" where f com url = do x <- await whenJust x $ \(i,s) -> case () of _ | Just s <- strStripPrefix "-- | " s -> f [s] url | Just s <- strStripPrefix "--" s -> f (if null com then [] else strTrimStart s : com) url | Just s <- strStripPrefix "@url " s -> f com (strUnpack s) | strNull $ strTrimStart s -> f [] "" | otherwise -> do case parseLine $ fixLine $ strUnpack s of Left y -> lift $ warning $ show i ++ ":" ++ y -- only check Nothing as some items (e.g. "instance () :> Foo a") -- don't roundtrip but do come out equivalent Right [EDecl InfixDecl{}] -> return () -- can ignore infix

Page 4: Elm: give it a try

It was Haskell

Page 5: Elm: give it a try

Real Elmresults : Signal.Mailbox (Result String (List String)) results = Signal.mailbox (Err "A valid US zip code is 5 numbers.")

port requests : Signal (Task x ()) port requests = Signal.map lookupZipCode query.signal |> Signal.map (\task -> Task.toResult task `andThen` Signal.send results.address)

lookupZipCode : String -> Task String (List String) lookupZipCode query = let toUrl = if String.length query == 5 && String.all Char.isDigit query then succeed ("http://api.zippopotam.us/us/" ++ query) else fail "Give me a valid US zip code!" in toUrl `andThen` (mapError (always "Not found :(") << Http.get places)

cene : (Int,Int) -> (Int,Int) -> Element scene (x,y) (w,h) = let (dx,dy) = (toFloat x - toFloat w / 2, toFloat h / 2 - toFloat y) in collage w h [ ngon 3 100 |> filled blue |> rotate (atan2 dy dx) , ngon 6 30 |> filled orange |> move (dx, dy) ]

Page 6: Elm: give it a try

Installation

npm install -g elm

elm-compilercompiler, yep

elm-makebuild tools

compile to JS or HTML build dependencies

elm-reactor dev tools

hot swap time travel debugging

elm-replets you interact with values and functions directly.

elm-packagepackage manager

Page 7: Elm: give it a try

JS > Elm | diff

string "functional" "functional" multiline non exists """functional programming""" string char non exists 'f' bool true/false True/False

> Literals

{ width: 300, height: 400} { width = 300, height = 400} worker.id = 12 { worker | id = 12 }

> Objects aka Records

function(x,y) { return x + y; } \x y -> x + y Math.max(3, 4) max 3 4 Math.min(1, Math.pow(2, 4)) min 1 (2^4) numbers.map(Math.sqrt) List.map sqrt numbers points.map(function(p) { return p.x }) List.map .x points

> Functions

Page 8: Elm: give it a try

JS > Elm | diff

'NCC-' + '1701' "NCC-" ++ "1701" 'NCC-'.length String.length "NCC-" 'NCC-'.toLowerCase() String.toLower "NCC-" 'NCC-' + 1701 "NCC-" ++ toString 1701

> Work with strings

2 > 1 ? 'Lee' : 'Chan' if 3 > 2 then "Lee" else "Chan" var x = 42; let x = 42 return 42 Everything is an expression, no need for return

> Control Flow

Page 9: Elm: give it a try

Core

> 9 / 24.5 : Float> 9 // 24 : Int>

> Values

> isNegative n = n < 0<function>

> isNegative 4False

> isNegative -7True

> isNegative (-3 * -4)False

> Functions

Page 10: Elm: give it a try

Data Structures

> Lists

> names = [ "Alice", "Bob", "Chuck" ]["Alice","Bob","Chuck"]

> List.isEmpty namesFalse

> List.length names3

> List.reverse names["Chuck","Bob","Alice"]

> numbers = [1,4,3,2][1,4,3,2]

> List.sort numbers[1,2,3,4]

> double n = n * 2<function>

> List.map double numbers[2,8,6,4]

Page 11: Elm: give it a try

List != Objectelm != OOP

List = Module

Page 12: Elm: give it a try

List modulemodule List ( isEmpty, length, reverse, member , head, tail, filter, take, drop , repeat, (::), append, concat, intersperse , partition, unzip , map, map2, map3, map4, map5 , filterMap, concatMap, indexedMap , ……………

{-| Determine if a list is empty. isEmpty [] == True -} isEmpty : List a -> Bool isEmpty xs = case xs of [] -> True

_ -> False

{-| Keep only elements that satisfy the predicate. filter isEven [1..6] == [2,4,6] -} filter : (a -> Bool) -> List a -> List a filter pred xs = let conditionalCons x xs' = if pred x then x :: xs'

else xs' in foldr conditionalCons [] xs

Page 13: Elm: give it a try

Data Structures

> Tuples

> import String

> goodName name = \ | if String.length name <= 20 then \ | (True, "name accepted!") \ | else \

| (False, "name was too long; please limit it to 20 characters")

> goodName "Tom" (True, "name accepted!")

Page 14: Elm: give it a try

Data Structures

> Records

> point = { x = 3, y = 4 } { x = 3, y = 4 }

> point.x 3

> bill = { name = "Gates", age = 57 } { age = 57, name = "Gates" }

> bill.name “Gates"

> .name bill "Gates"

> List.map .name [bill,bill,bill] ["Gates","Gates","Gates"]

Page 15: Elm: give it a try

Data Structures

> Records

> under70 {age} = age < 70 <function>

> under70 bill True

> under70 { species = "Triceratops", age = 68000000 } False

> { bill | name = "Nye" } { age = 57, name = "Nye" }

> { bill | age = 22 } { age = 22, name = "Gates" }

Page 16: Elm: give it a try

JS Object vs Elm Record

- You cannot ask for a field that does not exist. - No field will ever be undefined or null. - You cannot create recursive records with a this or self keyword.

Page 17: Elm: give it a try

Contractsimport String

fortyTwo : Int fortyTwo = 42

drinks : List String drinks = [ "Hennessy", "JimBeam", "Jack Daniels" ]

book : { title: String, author: String, pages: Int } book = { title = "Robert", author = "Martin", pages = 434 }

longestName : List String -> Int longestName drinks = List.maximum (List.map String.length drinks)

isLong : { record | pages : Int } -> Bool isLong book = book.pages > 400

Page 18: Elm: give it a try

All of these types can be inferred, so you can leave off the type annotations and Elm can still check that data is flowing around in a way that works. This means you can just not write these contracts and still get all the

benefits!

Page 19: Elm: give it a try

Enumerations

type Visibility = All | Active | Completed

toString : Visibility -> String toString visibility = case visibility of All -> "All"

Active -> "Active"

Completed -> "Completed"

-- toString All == "All" -- toString Active == "Active" -- toString Completed == "Completed"

Page 20: Elm: give it a try

State Machinestype User = Anonymous | LoggedIn String

userPhoto : User -> String userPhoto user = case user of Anonymous -> "anon.png"

LoggedIn name -> "users/" ++ name ++ "/photo.png"

Page 21: Elm: give it a try

Architecture

Model

View

Action

Page 22: Elm: give it a try

MUV-- MODEL

type alias Model = { ... }

-- UPDATE

type Action = Reset | ...

update : Action -> Model -> Model update action model = case action of Reset -> ... ...

-- VIEW

view : Model -> Html view = ...

Page 23: Elm: give it a try

Example

type alias Model = { topic : String , gifUrl : String }

init : String -> (Model, Effects Action) init topic = ( Model topic "assets/waiting.gif" , ge

update : Action -> Model -> (Model, Effects Action) update action model = case action of RequestMore -> (model, getRandomGif model.topic)

NewGif maybeUrl -> ( Model model.topic (Maybe.withDefault model.gifUrl maybeUrl) , Effects.none )

Model

Update

Page 24: Elm: give it a try

ExamplegetRandomGif : String -> Effects Action getRandomGif topic = Http.get decodeUrl (randomUrl topic) |> Task.toMaybe |> Task.map NewGif |> Effects.task

randomUrl : String -> String randomUrl topic = Http.url "http://api.giphy.com/v1/gifs/random" [ "api_key" => "dc6zaTOxFJmzC" , "tag" => topic ]

decodeUrl : Json.Decoder String decodeUrl = Json.at ["data", "image_url"] Json.string

Effects

Page 25: Elm: give it a try

Functional stuff, I got it

Page 26: Elm: give it a try

App flow

Page 27: Elm: give it a try

Signals

Page 28: Elm: give it a try

Signalsmodule Signal ( Signal , merge, mergeMany , map, map2, map3, map4, map5 , constant , dropRepeats, filter, filterMap, sampleOn , foldp , Mailbox, Address, Message , mailbox, send, message, forwardTo ) where

Page 29: Elm: give it a try

Tasks

elm-http — talk to serverselm-history — navigate browser historyelm-storage — save info in the users browser

Page 30: Elm: give it a try

elm-storage

import Native.Storage

getItemAsJson : String -> Task String Value getItemAsJson = Native.Storage.getItemAsJson

-- Do better error detection getItem : String -> Decoder value -> Task String value getItem key decoder = let decode value = case decodeValue decoder value of Ok v -> succeed v Err err -> fail "Failed" in getItemAsJson key `andThen` decode

Page 31: Elm: give it a try

Native.Storage aka Storage.js/*! localForage -- Offline Storage, Improved Version 1.2.2 https://mozilla.github.io/localForage (c) 2013-2015 Mozilla, Apache License 2.0 */ (function() { var define, requireModule, require, requirejs;

(function() { var registry = {}, seen = {};

define = function(name, deps, callback) { registry[name] = { deps: deps, callback: callback }; };

requirejs = require = requireModule = function(name) { requirejs._eak_seen = registry;

if (seen[name]) { return seen[name]; } seen[name] = {};

Page 32: Elm: give it a try

Mailbox

type alias Mailbox a = { signal : Signal a , address : Address a }

send : Address a -> a -> Task x ()

import Graphics.Element exposing (Element, show) import Task exposing (Task, andThen) import TaskTutorial exposing (getCurrentTime, print)

main : Signal Element main = Signal.map show contentMailbox.signal

contentMailbox : Signal.Mailbox String contentMailbox = Signal.mailbox ""

port updateContent : Task x () port updateContent = Signal.send contentMailbox.address "hello!"

Init�with�empty�value

send�a�message

signature

Page 33: Elm: give it a try

Ports - Communication between Elm and pure JS

Page 34: Elm: give it a try

Ports | JS > Elm

port addUser : Signal (String, UserRecord)

> Elm

> JS

myapp.ports.addUser.send([ "Tom", { age: 32, job: "lumberjack" } ]);

myapp.ports.addUser.send([ "Sue", { age: 37, job: "accountant" } ]);

Page 35: Elm: give it a try

Ports | Elm > JS

port requestUser : Signal String port requestUser = signalOfUsersWeWantMoreInfoOn

> Elm

> JS

myapp.ports.requestUser.subscribe(databaseLookup);

function databaseLookup(user) { var userInfo = database.lookup(user); myapp.ports.addUser.send(user, userInfo); }

Page 36: Elm: give it a try
Page 37: Elm: give it a try

HTML generation

main : Signal Element main = Signal.map2 renderStamps Window.dimensions clickLocations

clickLocations : Signal (List (Int,Int)) clickLocations = Signal.foldp (::) [] (Signal.sampleOn Mouse.clicks Mouse.position)

renderStamps : (Int,Int) -> List (Int,Int) -> Element renderStamps (w,h) locs = let pentagon (x,y) = ngon 5 20 |> filled (hsla (toFloat x) 0.9 0.6 0.7) |> move (toFloat x - toFloat w / 2, toFloat h / 2 - toFloat y) |> rotate (toFloat x) in layers [ collage w h (List.map pentagon locs) , show "Click to stamp a pentagon." ]

elm-make Stamper.elm --output=Main.html

Page 38: Elm: give it a try

Nice architecture

Good documentation

Source code with comments

Great developer toolset

Own packages / manager

Overview from JS perspective

Page 39: Elm: give it a try
Page 40: Elm: give it a try

Complex syntax

Learning curve

Team integration cost?

Overview from JS perspective

Page 41: Elm: give it a try

That's all folks

[email protected] @2j2e