From Elm 0.16 to Elm 0.17

The story of two migrations

Tech Lead @ Meetic

k.lebrun@meetic-corp.com

kevinlebrun

Berlin Clock

3-steps migration

Use a local Elm

$ ./node_modules/.bin/elm-package install
$ ./node_modules/.bin/elm-make Main.elm --output index.html
$ elm package install
$ elm make Main.elm --output index.html

Before

After

Use StartApp and Effects

type alias Seconds =
  Int

type State
  = On
  | Off

type alias Model =
  { top : State
  , line1 : List State
  , line2 : List State
  , line3 : List State
  , line4 : List State
  }


model : Seconds -> Model

view : Model -> Html


main : Signal Html
main =
  Signal.map (view << model) tick

tick : Signal Seconds
tick =
  Signal.map (round << Time.inSeconds) <| Time.every Time.second
+import StartApp
+import Task
+import Effects exposing (Effects, Never)
 
...
 
-view : Model -> Html
-view model =
+view : Signal.Address Seconds -> Model -> Html
+view address model =

...
 
-main : Signal Html
+app =
+  StartApp.start
+    { init = (model 0, Effects.none)
+    , view = view
+    , update = update
+    , inputs = [tick]
+    }
+
+
 main =
-  Signal.map (view << model) tick
+  app.html
+
+port tasks : Signal (Task.Task Never ())
+port tasks =
+  app.tasks
+
+
+update time _ =
+  (model time, Effects.none)

Migrate to Elm 0.17

    "dependencies": {
-        "elm-lang/core": "3.0.0 <= v < 4.0.0",
-        "evancz/elm-effects": "2.0.1 <= v < 3.0.0",
-        "evancz/elm-html": "4.0.2 <= v < 5.0.0",
-        "evancz/start-app": "2.0.2 <= v < 3.0.0"
+        "elm-lang/core": "4.0.0 <= v < 5.0.0",
+        "elm-lang/html": "1.0.0 <= v < 2.0.0"
     },
-    "elm-version": "0.16.0 <= v < 0.17.0"
-} 
+    "elm-version": "0.17.0 <= v < 0.18.0"

Start with dependencies

-    "elm": "^0.16.0"
+    "elm": "^0.17.0"

package.json

elm-package.json

-module Main (..) where
+module Main exposing (..)
 
...

-import StartApp
-import Task
-import Effects exposing (Effects, Never)
+import Html.App as Html

Simplify the imports

-view : Signal.Address Seconds -> Model -> Html
-view address model =
+view : Model -> Html Seconds
+view model =

Update the views

-app =
-  StartApp.start
-    { init = (model 0, Effects.none)
+main =
+  Html.program
+    { init = (model 0, Cmd.none)
     , view = view
     , update = update
-    , inputs = [tick]
+    , subscriptions = subscriptions
     }
 
 
-main =
-  app.html
-
-port tasks : Signal (Task.Task Never ())
-port tasks =
-  app.tasks
-
-
 update time _ =
-  (model time, Effects.none)
+  (model time, Cmd.none)

Reduce the noise

-tick : Signal Seconds
-tick =
-  Signal.map (round << Time.inSeconds) <| Time.every Time.second
+subscriptions : Model -> Sub Seconds
+subscriptions model =
+  Sub.map (round << Time.inSeconds) <| Time.every Time.second (\a -> a)

Transform signals into subscriptions

The result

Pocket Triage

New terms

-update : Action -> Model -> ( Model, Effects Action )
+update : Msg -> Model -> ( Model, Cmd Msg )

Compose views

-      , Selector.view (Signal.forwardTo address Link) model.snapshot
+      , Html.App.map Link <| Selector.view model.snapshot

Transform signals into subscriptions

-keyboard : Signal Action
+keyboard : Sub Msg
 keyboard =
   let
-    keyToAction key =
+    keyToMsg key =
       if key == 13 then
         Next
       else
-        Link (Selector.keyToAction key)
+        Link (Selector.keyToMsg key)
   in
-    Signal.map keyToAction Keyboard.presses
+    Keyboard.presses keyToMsg

+subscriptions model =
+  keyboard

Create commands from tasks

-getLinks : String -> Task Http.Error ()
+getLinks : String -> Task Http.Error (List Link)
 getLinks token =
   (Http.fromJson Link.decodeLinks
     <| authed "GET" token "http://localhost:8080/links" Http.empty
   )
     `andThen` (\dict -> succeed (Dict.values dict))
-    `andThen` (OnReceiveLinks >> Signal.send actions.address)
-    `onError` (toString >> HttpError >> Signal.send actions.address)
 
+doGetLinks token =
+  Task.perform (toString >> HttpError) OnReceiveLinks <| getLinks token

Program flags I

- var app = Elm.embed(Elm.Triage, div, { getToken: token });
+ var app = Elm.Triage.embed(div, { token: token });

Program flags II

-app =
-  StartApp.start
-    { init = ( emptyModel, Effects.none )
+main : Program { token : Maybe String }
+main =
+  Html.App.programWithFlags
+    { init = init
     , view = view
     , update = update
-    , inputs = [ keyboard, actions.signal ]
+    , subscriptions = subscriptions
     }


+init flags =
+  let
+    cmd =
+      case flags.token of
+        Just token ->
+          doGetLinks token
+        Nothing ->
+          Cmd.none
+  in
+    ( emptyModel flags.token, cmd )


-port getToken : Maybe String

Outgoing ports I

update action model =
    case action of
      Next ->

...

-        cmd = Effects.batch [deleteEffect token deleted, sendDeleted deleted]
+        cmd = Cmd.batch [doDelete token deleted, Ports.deletedLinks deleted]

...

-deleted : Signal.Mailbox (List Link)
-deleted =
-  Signal.mailbox []

-sendDeleted links =
-  Signal.send deleted.address links
-    |> Task.map (always NoOp)
-    |> Effects.task

-port deletedLinks : Signal (List Link)
-port deletedLinks =
-  deleted.signal

Outgoing ports II

+port module Ports exposing (..)
+
+import Link exposing (Link)
+
+
+port deletedLinks : List Link -> Cmd msg

The result

Conclusion

Q/A

k.lebrun@meetic-corp.com

kevinlebrun

From Elm 0.16 to Elm 0.17

By Kevin Le Brun

From Elm 0.16 to Elm 0.17

The story of two migrations

  • 1,272