Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional in the Front, Rails in the Back: the...

Functional in the Front, Rails in the Back: the Elm Mullet

Presentation given at RubyConf AU 2017.

Avatar for Louis Simoneau

Louis Simoneau

February 10, 2017

Other Decks in Technology

Transcript

  1. What is elm? A delightful language for reliable webapps. Generate

    JavaScript with great performance and no runtime exceptions.
  2. No Runtime Errors type alias User = { name: String

    , state: String , phone: String }
  3. No Runtime Errors type alias User = { name: String

    , state: String , phone: String } areaCodes = Dict.fromList [ ("ACT", "02") , ("NSW", "02") , ("NT", "08") , ("QLD", "07") , ("SA", "08") , ("VIC", "03") , ("WA", "08") ]
  4. No Runtime Errors type alias User = { name: String

    , state: String , phone: String } areaCodes = Dict.fromList [ ("ACT", "02") , ("NSW", "02") , ("NT", "08") , ("QLD", "07") , ("SA", "08") , ("VIC", "03") , ("WA", "08") ] displayPhone user = (Dict.get user.state areaCodes) ++ user.phone
  5. No Runtime Errors type alias User = { name: String

    , state: String , phone: String } areaCodes = Dict.fromList [ ("ACT", "02") , ("NSW", "02") , ("NT", "08") , ("QLD", "07") , ("SA", "08") , ("VIC", "03") , ("WA", "08") ] displayPhone user = (Dict.get user.state areaCodes) ++ user.phone
  6. No Runtime Errors displayPhone user = case (Dict.get user.state areaCodes)

    of Just areaCode -> areaCode ++ user.phone Nothing -> user.phone ++ " (area code unknown)"
  7. No Runtime Errors displayPhone user = case (Dict.get user.state areaCodes)

    of Just areaCode -> areaCode ++ user.phone Nothing -> user.phone ++ " (area code unknown)"
  8. No Runtime Errors displayPhone user = case (Dict.get user.state areaCodes)

    of Just areaCode -> areaCode ++ user.phone Nothing -> user.phone ++ " (area code unknown)"
  9. No Runtime Errors displayPhone user = case (Dict.get user.state areaCodes)

    of Just areaCode -> areaCode ++ user.phone Nothing -> user.phone ++ " (area code unknown)"
  10. “Elm is designed to be fun to use.” - Evan

    Czaplicki… …probably said something like this
  11. Ruby Elm Elm Syntax (Literals) > 42 => 42 irb(main):002:0>

    1.618 => 1.618 > 42 42 : number > 1.618 1.618 : Float
  12. Ruby Elm Elm Syntax (Literals) > 42 => 42 irb(main):002:0>

    1.618 => 1.618 > 42 42 : number > 1.618 1.618 : Float
  13. Ruby Elm Elm Syntax (Literals) > 42 => 42 >

    1.618 => 1.618 > 42 42 : number > 1.618 1.618 : Float
  14. Ruby Elm Elm Syntax (Literals) > 42 => 42 >

    1.618 => 1.618 > 42 42 : number > 1.618 1.618 : Float
  15. Ruby Elm Elm Syntax (Literals) > "Hello" => "Hello" irb(main):004:0>

    'H' => "H" irb(main):005:0> 'Hello' => "Hello" > "Hello" "Hello" : String > 'H' 'H' : Char > 'Hello' -- SYNTAX PROBLEM
  16. Ruby Elm Elm Syntax (Literals) > "Hello" => "Hello" irb(main):004:0>

    'H' => "H" irb(main):005:0> 'Hello' => "Hello" > "Hello" "Hello" : String > 'H' 'H' : Char > 'Hello' -- SYNTAX PROBLEM
  17. Ruby Elm Elm Syntax (Literals) > "Hello" => "Hello" >

    'H' => "H" irb(main):005:0> 'Hello' => "Hello" > "Hello" "Hello" : String > 'H' 'H' : Char > 'Hello' -- SYNTAX PROBLEM
  18. Ruby Elm Elm Syntax (Literals) > "Hello" => "Hello" >

    'H' => "H" irb(main):005:0> 'Hello' => "Hello" > "Hello" "Hello" : String > 'H' 'H' : Char > 'Hello' -- SYNTAX PROBLEM
  19. Ruby Elm Elm Syntax (Literals) > "Hello" => "Hello" >

    'H' => "H" > 'Hello' => "Hello" > "Hello" "Hello" : String > 'H' 'H' : Char > 'Hello' -- SYNTAX PROBLEM
  20. Ruby Elm Elm Syntax (Literals) > "Hello" "Hello" : String

    > 'H' 'H' : Char > 'Hello' -- SYNTAX PROBLEM > "Hello" => "Hello" > 'H' => "H" > 'Hello' => "Hello"
  21. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH
  22. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH
  23. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH
  24. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH
  25. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH
  26. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH
  27. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH
  28. Ruby Elm Elm Syntax (List) > [1,2,3] => [1, 2,

    3] > [1,2,3].map { |n| n + 1 } => [2, 3, 4] > [1, "ABC", 3] => [1, "ABC", 3] > [1, "ABC", 3].map { |n| n + 1 } TypeError: no implicit conversion of Integer into String > [1,2,3] [1,2,3] : List number > List.map (\n -> n + 1) [1,2,3] [2,3,4] : List number > [1, "ABC", 3] -- TYPE MISMATCH > List.map (\n -> n + 1) [1,'ABC',3] -- Avoid Runtime Errors by Design
  29. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  30. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  31. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  32. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  33. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  34. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  35. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  36. Ruby Elm Elm Syntax (Record) > point = OpenStruct.new(x:4,y:2) =>

    #<OpenStruct x=4, y=2> > point.x => 4 > point.x = 1 => 1 > point.z = 3 => 3 > point = { x = 4, y = 2 } { x = 4, y = 2 } : { x : number, y : number1 } > point.x 4 : number > { point | x = 1 } { x = 1, y = 2 } : { y : number1, x : number } > { point | z = 1 } -- TYPE MISMATCH
  37. Ruby Elm Elm Syntax (Functions) > def add(x, y) x

    + y end => :add > add 1, 2 => 3 > add x y = x + y <function> : number -> number -> number > add 1 2 3 : number
  38. Ruby Elm Elm Syntax (Functions) > def add(x, y) x

    + y end => :add > add 1, 2 => 3 > add x y = x + y <function> : number -> number -> number > add 1 2 3 : number
  39. Ruby Elm Elm Syntax (Functions) > def add(x, y) x

    + y end => :add > add 1, 2 => 3 > add x y = x + y <function> : number -> number -> number > add 1 2 3 : number
  40. Ruby Elm Elm Syntax (Functions) > def add(x, y) x

    + y end => :add > add 1, 2 => 3 > add x y = x + y <function> : number -> number -> number > add 1 2 3 : number
  41. ELM Syntax (Type Annotation) greet : String -> String greet

    name = "Hello, " ++ name ++ "!" 
 square : number -> number square number = number * number
 
 add : number -> number -> number add x y = x + y
  42. ELM Syntax (Type Annotation) greet : String -> String greet

    name = "Hello, " ++ name ++ "!" 
 square : number -> number square number = number * number
 
 add : number -> number -> number add x y = x + y
  43. Elm (Currying) add : number -> number -> number add

    x y = x + y > add 1 2 3 : number > add 1 <function> : number -> number > ((add 1) 2) 3 : number
  44. Elm (Currying) add : number -> number -> number add

    x y = x + y > add 1 2 3 : number > add 1 <function> : number -> number > ((add 1) 2) 3 : number
  45. Elm (Currying) add : number -> (number -> number) add

    x y = x + y > add 1 2 3 : number > add 1 <function> : number -> number > ((add 1) 2) 3 : number
  46. Elm (Currying) add : number -> number -> number add

    x y = x + y > add 1 2 3 : number > add 1 <function> : number -> number > ((add 1) 2) 3 : number
  47. ELM Syntax (Type Alias) name : { firstName : String,

    lastName : String } -> String name person = person.firstName ++ " " ++ person.lastName
  48. ELM Syntax (Type Alias) type alias Person = { firstName

    : String, lastName : String } name : Person -> String name person = person.firstName ++ " " ++ person.lastName
  49. ELM Syntax (Type Alias) type alias Person = { firstName

    : String, lastName : String } name : Person -> String name person = person.firstName ++ " " ++ person.lastName
  50. ELM Syntax (Union Types) type User = Anonymous | Named

    String Anonymous -- Anonymous : User Named -- <function> : String -> User Named "Marvin" --Named "Marvin" : User
  51. ELM Syntax (Union Types) type User = Anonymous | Named

    String Anonymous -- Anonymous : User Named -- <function> : String -> User Named "Marvin" --Named "Marvin" : User
  52. ELM Syntax (Union Types) type User = Anonymous | Named

    String Anonymous -- Anonymous : User Named -- <function> : String -> User Named "Marvin" --Named "Marvin" : User
  53. ELM Syntax (Union Types) type User = Anonymous | Named

    String Anonymous -- Anonymous : User Named -- <function> : String -> User Named "Marvin" --Named "Marvin" : User
  54. ELM Syntax (Union Types) userPhoto : User -> String userPhoto

    user = case user of Anonymous -> "anonymous.png" Named name -> "users/" ++ name ++ ".png"
  55. ELM Syntax (Union Types) userPhoto : User -> String userPhoto

    user = case user of Anonymous -> "anonymous.png" Named name -> "users/" ++ name ++ ".png"
  56. ELM Syntax (Union Types) userPhoto : User -> String userPhoto

    user = case user of Anonymous -> "anonymous.png" Named name -> "users/" ++ name ++ ".png"
  57. ELM Syntax (Union Types) userPhoto : User -> String userPhoto

    user = case user of Anonymous -> "anonymous.png" Named name -> "users/" ++ name ++ ".png"
  58. ELM Syntax (Union Types) type Maybe a = Nothing |

    Just a type alias User = { firstName : String, middleName : Maybe String, lastName : String } { firstName = "Douglas", middleName = Nothing, lastName = "Adams" } { firstName = "Douglas", middleName = Just "Noel", lastName = "Adams" }
  59. ELM Syntax (Union Types) type Maybe a = Nothing |

    Just a type alias User = { firstName : String, middleName : Maybe String, lastName : String } { firstName = "Douglas", middleName = Nothing, lastName = "Adams" } { firstName = "Douglas", middleName = Just "Noel", lastName = "Adams" }
  60. ELM Syntax (Union Types) type Maybe a = Nothing |

    Just a type alias User = { firstName : String, middleName : Maybe String, lastName : String } { firstName = "Douglas", middleName = Nothing, lastName = "Adams" } { firstName = "Douglas", middleName = Just "Noel", lastName = "Adams" }
  61. ELM Syntax (Union Types) type Maybe a = Nothing |

    Just a type alias User = { firstName : String, middleName : Maybe String, lastName : String } { firstName = "Douglas", middleName = Nothing, lastName = "Adams" } { firstName = "Douglas", middleName = Just "Noel", lastName = "Adams" }
  62. ELM Syntax (Union Types) type Result error value = Err

    error | Ok value String.toInt : String -> Result String Int String.toInt "123" == Ok 123 String.toInt "-42" == Ok -42 String.toInt "3.1" == Err "could not convert string '3.1' to an Int" String.toInt "31a" == Err "could not convert string '31a' to an Int”
  63. ELM Syntax (Union Types) type Result error value = Err

    error | Ok value String.toInt : String -> Result String Int String.toInt "123" == Ok 123 String.toInt "-42" == Ok -42 String.toInt "3.1" == Err "could not convert string '3.1' to an Int" String.toInt "31a" == Err "could not convert string '31a' to an Int”
  64. ELM Syntax (Union Types) type Result error value = Err

    error | Ok value String.toInt : String -> Result String Int String.toInt "123" == Ok 123 String.toInt "-42" == Ok -42 String.toInt "3.1" == Err "could not convert string '3.1' to an Int" String.toInt "31a" == Err "could not convert string '31a' to an Int”
  65. ELM Syntax (Union Types) type Result error value = Err

    error | Ok value String.toInt : String -> Result String Int String.toInt "123" == Ok 123 String.toInt "3.1" == Err "could not convert string '3.1' to an Int" String.toInt "31a" == Err "could not convert string '31a' to an Int”
  66. Html.beginnerProgram type alias Model = Int type Msg = Increment

    | Decrement update : Msg -> Model -> Model update = ...
  67. Html.beginnerProgram type alias Model = Int type Msg = Increment

    | Decrement update : Msg -> Model -> Model update = ... view : Model -> Html Msg view = ...
  68. Html.beginnerProgram type alias Model = Int type Msg = Increment

    | Decrement update : Msg -> Model -> Model update = ... view : Model -> Html Msg view = ... Html.beginnerProgram { model: 0 , update: update , view: view }
  69. The Model type alias Item = { quantity: Int ,

    name: String , unit_price: Float , subtotal: Float }
  70. The Model type alias Item = { quantity: Int ,

    name: String , unit_price: Float , subtotal: Float } type alias ProductId = Int
  71. The Model type alias Item = { quantity: Int ,

    name: String , unit_price: Float , subtotal: Float } type alias ProductId = Int type alias Model = Dict ProductId Item
  72. The Model model : Model model = Dict.fromList [ (

    123 , { quantity: 1 , unit_price: 99.0 , subtotal: 99.0 , name: "Sulfuras, Hand of Ragnaros" } ) ]
  73. The Update type Msg = Increment ProductId | Decrement ProductId

    update : Msg -> Model -> Model update msg model = case msg of Increment productId -> updateProductCount productId model 1 Decrement productId updateProductCount productId model -1
  74. The View itemView : ( ProductId, Item ) -> Html

    Msg itemView ( productId, item ) = div [] [ div [] [ text item.name ] , div [] [ toString item.quantity |> text ] , button [ onClick (Decrement productId) ] [ text "-" ] , button [ onClick (Increment productId) ] [ text "+" ] ]
  75. The View view : Model -> Html Msg view model

    = div [] (List.map itemView (Dict.toList model)) itemView : ( ProductId, Item ) -> Html Msg itemView ( productId, item ) = div [] [ div [] [ text item.name ] , div [] [ toString item.quantity |> text ] , button [ onClick (Decrement productId) ] [ text "-" ] , button [ onClick (Increment productId) ] [ text "+" ] ]
  76. Update with Commands update : Msg -> Model -> (

    Model, Cmd Msg ) update msg model =
  77. Update with Commands update : Msg -> Model -> (

    Model, Cmd Msg ) update msg model =
  78. Update with Commands update : Msg -> Model -> (

    Model, Cmd Msg ) update msg model = case msg of GoToCheckout -> ( { model | status = ViewingCheckoutForm }, Cmd.none ) CancelCheckout -> ( { model | status = ViewingCart }, Cmd.none )
  79. Update with Commands update : Msg -> Model -> (

    Model, Cmd Msg ) update msg model = case msg of GoToCheckout -> ( { model | status = ViewingCheckoutForm }, Cmd.none ) CancelCheckout -> ( { model | status = ViewingCart }, Cmd.none )
  80. Update with Commands update : Msg -> Model -> (

    Model, Cmd Msg ) update msg model = case msg of GoToCheckout -> ( { model | status = ViewingCheckoutForm }, Cmd.none ) CancelCheckout -> ( { model | status = ViewingCart }, Cmd.none ) Increment productId -> ( model, updateItem productId 1 ) Decrement productId -> ( model, updateItem productId -1 )
  81. Update with Commands update : Msg -> Model -> (

    Model, Cmd Msg ) update msg model = case msg of GoToCheckout -> ( { model | status = ViewingCheckoutForm }, Cmd.none ) CancelCheckout -> ( { model | status = ViewingCart }, Cmd.none ) Increment productId -> ( model, updateItem productId 1 ) Decrement productId -> ( model, updateItem productId -1 ) GotCartResponse (Ok cart) -> ( { model | cart = cart }, Cmd.none ) GotCartResponse (Err error) -> ( { model | cartError = Just (displayError error) }, Cmd.none )