layout: true name: inverse class: center, middle, inverse .header[.floatleft[.teal[Christopher Biggs] — Functional SPAs].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[lambdajam] May 2019]] --- layout: true name: callout class: center, middle, italic, bulletul .header[.floatleft[.teal[Christopher Biggs] — Functional SPAs].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[lambdajam] May 2019]] --- template: inverse # Rapid Web productivity in Elm ## .green[Functional Single Page Apps, and more] .bottom.right[ Christopher Biggs, .logo[Accelerando Consulting]
@unixbigot .logo[@accelerando_au] ] ??? G'day folks, I'm Christopher Biggs. And today I want to talk about how functional programming applies to the world wide web, and delivers on its promises of productivity and safety. --- # Who am I? ## Christopher Biggs — .teal[@unixbigot] — .logo[@accelerando_au] * Brisbane, Australia * Founder, .logo[Accelerando Consulting] * 20+ years in IT as developer, architect, manager * Accelerando is a "full service" consultancy - chips to cloud * ***IoT, DevOps, Big Data*** ??? I run a business called Accelerando which is all about rapid prototyping. We use technology to reduce stress by getting computers to worry about things so that you don't have to. And this mindset extends to our taste in development tools. --- # Agenda * What is Elm * Why not React/Redux? * What's new in Elm * Single page apps (SPAs) * SPAs in Elm * Material Design in Elm * Integrating vanilla Javascript * Migrating to Elm 19 ??? Today I'm going to give you a capsule introduction to the philosophy of the elm language and a look at its syntax. I'm not going to try and teach the language, but rather look at how to get started in learning it yourself, and why you might want to try. We'll do this in the context of writing a simple real world application leveraging the features in the latest release of the elm runtime. Along the way we'll look at how elm fits into the wider javascript ecosystem, and what you might need to know if you've been developing in a previous version of Elm. --- layout: true template: callout .crumb[ # Welcome # Elm ] --- template: inverse # What is Elm ??? For those of you who are new to Elm, it arose from a university thesis on functional reactive programming by a student named Evan Czaplicki. I know what you're thinking. It's one of those toy academic functional languages that is pretty as kitten with a pink ribbon but about as useful as a meat raincoat in a tornado full of hyenas. Except it's not. --- # Practical Functional Programming for the web ??? Elm is a remarkably useful and practical functional language which compiles to javascript, runs in the web browser, and does one thing better than any of its competitors. That thing is safety. The promise of Elm is no runtime errors, ever. You never get that thrice damned null object error. And that's not just because Elm doesn't have objects. --- # Elm's functional cred * Immutable data * Strong typing * Algebraic data types * Higher order functions * No* side-effects .footnote[pay no attention to the side-effects behind the curtain] ??? The way Elm does this is by being pretty darn strict about what you can do. It's a small language, with a simple but not *too* simple type system. It's about as much of haskell as you could write on the back of a postcard. We have immutable data, no variable aliasing bugs. Strong typing, no data type or missing method errors. Algebraic data types, which let you express ambiguitiy inside the language, rather than in your head. No null pointer errors. Higher order functions and extensive use of currying and composition provide a surprisingly rich palette of operations from a quite small standard library. Side effects are very strongly constrained, and the upshot of this is that the language actively helps you to cover all possible edge cases. --- # .ul[T]he .ul[E]lm .ul[A]rchitecture ## (A nice hot cup of TEA) ??? So what is working in Elm like? The elm core libraries enforce a structure to your program that funnels all the complexity into one place, and uses the type system to insist that you handle uncertainty meticulously. The compiler has got your back. The safety features of the language mean that refactoring is your go to tool. Rather than designing an architecture complex enough for every eventuality as would be common in a pure Javascript framework, Elm encourages you to begin with a blank page and iterate fast, with the knowledge that if the compiler okays your program, it will run. Might even run where you want it to go. --- # This talk is .ul[**not**] an introduction to Elm. ## But, for those who came late... a precis * Model * Update * View That's it*. .footnote[sorta] ??? In an elm program you have exactly one variable, which we call the model. Messages from the outside world update your model, and the update operation emits a new model, and potentially some actions. You don't have to worry about updating the screen, because the language handles it. Each time the model changes your view routine is called to render the state of your model as a web page, and then the language runtime efficiently updates the document object to render the changed page. There's a good helping of virtual document object models, tree shaking and all the cool buzzwords to make what sounds like an insane idea actually be efficient. --- # An instructive made-up* example ## Configuring video distribution hardware for pubs .footnote[not so made-up] ??? Here's a made up example which is actually a cut down version of an app we wrote at Acelerando this year. We wrote it in react because handwavy reasons but the whole time we were doing that, I wondered whether it would have been easier or harder in Elm. So I set out to find out. This is an app for managing the video distribution hardware in a sports bar. If you've ever been unlucky enough to visit a pub this century, you may notice that there are TVs EVERYWHERE. Our client created a product that escalates our audiovisual nightmare by tunneling HDMI over multicast IP. They make two kinds of black boxes, one takes HDMI in and multicasts UDP. The other receives the multicast stream and spits out HDMI. So instead of running expensive HDMI cables everywhere, you just run Cat6 and install these boxes. Both transmitters and receivers can be set to a channel, which is pretty much just the multicast IP address. They're very simplistic, you give them an IP and a Name, set a channel, and that's it. --- # Our model .code[```elm type alias Model = { devices: List HdmiDevice , current: String } type alias HdmiDevice = { name: String , kind: DeviceKind , addr: IpAddress , mac: MacAddress , channel: Int } type DeviceKind = Transmitter | Receiver type IpAddress = UseDHCP | IPv4 Int type alias MacAddress = List Int ```] ??? Our model is just a list of devices that we know about, and which one we are currently looking at. Each device has a name, address, device kind and channel. That's about it. --- # An update function ### Receive message, alter model, emit action .code[```elm type Msg = ScanForDevices | FoundDevices (Result Http.Error (List HdmiDevice)) | SetDeviceChannel String Int | SetDeviceName String String | SetDeviceAddress String IpAddress update msg model = case msg of ScanForDevices -> (model, scanForDevices) FoundDevices (Ok dl) -> ({model | devices = dl}, Cmd.none) FoundDevices (Err _) -> (model, Cmd.none) SetDeviceChannel name channel -> ... SetDeviceName name newname -> ... SetDeviceAddress name address -> ... ```] ??? So what are the things that can change our model? Well at startup we scan the network for devices, and hopefully we find some. The whole point of this application is to change channels, so we have a message to achieve that, and in the installation process we want to give devices a meaningful name and address. --- ## A View: Render a representation of the model in browser .code[```elm import Html exposing (main_, h1, h2, div, ul, li, text) import Html.Attributes exposing (class) import List view model = let isTransmitter d = case d.kind of Transmitter -> True Receiver -> False (transmitters,receivers) = List.partition isTransmitter model.devices in main_ [] [ h1 [] [ text "HDMI Devices" ] , div [ class "transmitters"] [ h2 [] [text "Transmitters"] , deviceList transmitters ] , div [ class "receivers"] [ h2 [] [text "Receivers"] , deviceList receivers ] ] deviceList l = ul [] (List.map deviceInfo l) deviceInfo d = li d.name ```] ??? The update function is the core of your application. Every time update emits a changed model, the runtime calls the view function, which renders the model. Theres a whole raft of stuff that happens behind the curtain to avoid wasting time rendering unchanged views, but in practice you barely care, you just write your update and view functions as if cpu cycles are unlimited, and let the architecture handle the gritty details. The mess see up there is what happens when you take a literal minded approach to representing your model in HTML. Later on we'll look at some more idomatic approaches to rendering. --- layout: true template: callout .crumb[ # Welcome # Elm # React ] --- template: inverse # But that's just the same as React with Redux! ??? Right about now those of you who do web programming will be muttering that this all sounds an awful lot like a combination of component-based frameworks like React, and state-machine engines like Redux. You'd be right, but its arguably the other way around, a number of javascript frameworks including redux are a conscious attempt to port the Elm architecture to pure Javascript. --- # Kinda ## But with a more helpful compiler ??? In my experience, developing with react and redux can become an exercise in whack a mole. You do have the same concepts, Redux's store is the same as Elm's model. Redux's actions are Elm's commands, and Redux's reducers are Elm's update. But when you're working in Javascript it's all up to you, if you add an action and forget to update a reducer, nothing warns you. If you typo the name of an action, then well sunshine, you're in for an afternoon of giddy adventure chasing that rabbit. You get what I call the Star Wars effect. Beautiful architecture and NO SAFETY RAILS. --- # So I'll just use React with Typescript, or Flow ## Good idea, do that ??? But hey, there's bolt on type systems for javascript like Typescript and Flow, which can give you a partial safety net. Definitely evaluate one of those if you're havent' already. --- # Faster refactoring, less boilerplate ## Learning elm will help you write better Javascript, too ??? Even if you're going to stick with working in Javascript, its my opinion that giving Elm a try will improve the way you think about web applications, so I really encourage you to give it a go. --- # 🎵The Farmer and the Cowman Should Be Friends🎶 ## You can embed Elm components inside a React (or other JS) app .hugecode[```jsx import Elm from 'react-elm-components'; import {Chat} from '../dist/elm/chatroom.js'; function render() { return
; } ```] ??? So how would you go about dipping your toes into Elm? Well if you have an existing web app, you can elm-ify it one component at a time. Within your layout you render a blank component with a particular ID then you tell your compiled elm program to render into that div. But it's even easier now because there's a library to wrap an elm component in react. --- layout: true template: callout .crumb[ # Welcome # Elm # React # Elm 0.19 ] --- template: inverse # What's new in Elm 0.19? ??? Before we move on with our single page app, I want to throw you a mid season cliffhanger and change tack to talk about what's changed in the latest release of Elm, zero point nineteen. --- # Wait, did you say ZERO?! ## Yes (and no) ??? Yes, I said zero. Now Elm gets behind the semantic versioning movement, so zero really means zero. That scaffolding you were standing on might get removed without notice. But again this is a case where the architecture helps you out. --- # Major changes in 0.19 * Single command line entry point `elm` * `Browser` module codifies common web app patterns * Lots of modules renamed/combined * `String`<->`Number` conversion simplified * EVEN SMALLER output * EVEN FASTER compilation * REMOVAL of user defined operators ??? Let's briefly go through the changes in this release. The command line has had a major refactoring, merging all the entry points like elm-dash-make and elm-dash-package into a single command. So sorry, all your build scripts broke. On the good news side, previously there were five or ten modules you needed to import to do properly responsive webpages, and there was no one anointed way to structure a single-page web app. Some of those were core modules and some of them were just really useful third party modules. Now the most useful patterns are part of the core libraries, and all the essentials you need to make a response web app are unified under a module call Browser. There's been a simplification of string to number conversion, which reduces code complexity slightly without sacrificing safety. Some aggressive optimisation has led to faster compilation and smaller web objects. A compiled elm app can be a quarter of the size of an equivalent react app. The outputs are so small that we've deployed them in IoT modules the size of a postage stamp. And finally, you haskellers who have been inventing all kinds of wiggly arrow operators and strip mining the obscure reaches of obsolete alphabets for new symbols have ruined it for the rest of us, and the Elm team decided that user defined operators were more confusing than helpful and reverted support for them. If the comma was good enough for Turing, it's good enough for you. --- # Oh crap, so all my code breaks? ## No (and Yes) ??? So, you put all this effort into writing an Elm app and then the next release of the language totally broke it. Well, that's true, from a certain point of view. But, this is where the language comes to our rescue again; the vigourous suppression of side-effects means that automated refactoring is quite a tractable problem, so there's an automated migration tool that's part of the latest release, which can rewrite your program to resolve most of the breaking changes in the release. --- layout: true template: callout .crumb[ # Welcome # Elm # React # Elm 0.19 # SPAs ] --- template: inverse # Let's write a Single Page App ??? Okay, now lets come back to that single page app. But first I want to define my terms. What do I even mean by a single page app? Traditionally every time you clicked on anything in a web page you got a new pageload. Then AJAX came along and we could update parts of a page without reloads, only reloading when we want a whole new page. But AJAX broke the back button, if you modify so much state that a user thinks of the viewport as constituting a different page page, then they have a right to expect to be able to navigate between these pages and see them in their web history. We also want to retain the ability to do deep linking, to represent some internal state of an an application as a URL that can be requested as an entry point to our site. So to enable these goals Javascript gained the ability to manipulate the page URL and the browser history to make a single page look like navigating among distinct pages. This is what we call a single page app. It may look like a bunch of distinct pages with distinct URLs but it's just one pageload and then a bunch of API calls, which has two key benefits; firstly its faster, and secondly it opens up the possiblity to create web apps that work offline. --- # Boil your plates, and bend your forks ??? So how do we create a single page app in Elm. Well, just like anything else you google "how do I create a single page app in elm", and just like anything else you get a buttload of answers that are either wrong or out of date. In the Javascript world getting started can be so complex that there are number of boilerplate projects that do the work of bringing in all the tools and modules you will need to get out of the gate. There's plenty of single page app examples out there for Elm too, but in my experience this is the wrong approach. Building a complex structure too early in your project slows you down, and makes it harder to learn the language. So I prefer to start out with a single file, where I can see my whole app initially on one page. --- # create-elm-app ## Up and running in elm in two minutes .hugecode[```bash npm install -g elm create-elm-app create-elm-app myspa cd myspa elm-app start ```] ??? I've found one tool that I think splits the difference wonderfully. If you work in react you might have encountered a tool called create-react-app. It's darn near impossible to get started in a react project without it. Well, starting an elm project from an empty file is actually quite feasible, but some folks have taken inspiration from create-react-app and written a very useful tool called, funnily enough, create-elm-app. This takes care of setting up all the resources you'll need for application packaging and hot-reloading, but still leaves you with a single small elm file to begin writing your app. Any compiler errors are rendered in your browser window, so developing becomes editor here, browser there, and every time you save file, your page reloads, or breaks. --- .fig80[ ![](error.png)] .spacedn[ # Friendly errors] ??? But if it does break, the compiler messages are really helpful. If you forget to import a module, or a symbol, the compiler will suggest which module you might have wanted. If you misspell a symbol, it does the same. If your argument type or count is wrong on a function, the compiler tells you what is missing. Since we're making heavy use of partial function application, you'll come to appreciate the error message that says "whoops, looks like this function is looking for one more argument of type X". --- # Study some history ### Use .ul[`Browser.application`] to create single-page apps .code[```elm import Browser import Browser.Navigation as Nav type Msg = LinkClicked Browser.UrlRequest | UrlChanged Url.Url update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of LinkClicked urlRequest -> case urlRequest of Browser.Internal url -> ( model, Nav.pushUrl model.key (Url.toString url) ) Browser.External href -> ( model, Nav.load href ) UrlChanged url -> ( { model | url = url } , Cmd.none ) ```] ??? Now, lets look at how we manage browser history in an Elm single page app. When we create an elm program we tell it what kind of web element it is going to be. If it's embedded in a page we call Browser.element. For a standalone page we call Browser.document and for a single-page-app we call Browser.application. The extra wrinkle that application gives us is two messages that the runtime emits whenever the user initiates a url change, and whenever the browser completes a url change. This gives us the ability firstly to fudge the browser history, and secondly to extract state from the URL in order to handle deep linking and to render different views for different urls. --- # Putting down Routes .code[```elm import Url.Parser exposing (Parser, (>), int, map, oneOf, s, string) type Route = Home | Settings | Device String | DeviceSettings String | FourOhFour routeParser : Parser (Route -> a) a routeParser = oneOf [ map Home top , map Settings (s "settings" > int) , map Device (s "device" > string) , map DeviceSettings (s "device" > string > s "edit") ] ```] ??? It was a common pattern to abstract URLs as routes, and this is now an officially anointed patterh. In this case we define the possible kinds of page that we can display, and we map the input URL onto a route, extracting any neccessary parameters from the path, the query or any URL fragments. --- # Turning URLs into routes .code[```elm update msg model = case msg of UrlChanged url -> ( { model | url = url | route = Maybe.withDefault FourOhFour (parse route url) } , Cmd.none ) view model = case model.route of Home -> viewHome Settings -> viewSettings Device d -> viewDevice d DeviceSettings d -> editDevice d ```] ??? Now when we call our view function, we can make a top level decision on which kind of page we want to show the user, and dive off to different code for each page. You can go as far as separating each page into its own model and update, and again this is a pattern you would use out of the box with React and Redux, but in elm you can fly by the seat of your pants until and unless you think that the refactoring is warranted. I've never needed to do it, so far, though I'm getting there with one application we're still developing. --- # The cake contains a file .hugecode[```elm import File.Select as Select type Msg = ZipRequested | ZipLoaded File selectZip : Cmd Msg selectZip = Select.file ["application/zip"] ZipLoaded ```] ??? Another welcome feature in the latest Elm is file uploads, previously they weren't too difficult but were not exactly very obvious, you had to ask Javascript to initiate the file upload, then pass the uploaded file to elm and then base64 decode it. Now processing a file upload is literally a one line operation. This also works with drag and drop, progress notifications and image previews. I'm kind of excited about this feature, except now we have to rewrite a bunch of code that we bled over a year ago to get the behaviour we wanted. --- # A change I can GET behind In Elm 0.18: .code[```elm getWombles = let url = "http://localhost:8080/womble" request = Http.request { method = "GET" , headers = [Http.header "Accept" "application/json"] , url = url , body = Http.emptyBody , expect = Http.expectJson decodeWomble , timeout = Nothing , withCredentials = False } in Http.send GotWombles request ```] ??? There's also been a major simplification of http requests. Last year doing an API call looked like this. We created a HTTP request, and defined a handler to process its response. Then we emit the command to send the request, and later on expect to receive the processed response. --- # HTTP in Elm 0.19 .hugecode[```elm getWombles = Http.get { url = "http://localhost:8080/womble" , expect = Http.expectJson GotWombles decodeWomble } ```] ??? In the latest Elm it all happens in one step, you make the return message becomes part of the response handler, and you create and send a HTTP request in a single function call. --- # Web components in elm ??? There is one area where working in elm gets frustrating, and that's component frameworks. There's not a huge choice, and a faction of the elm community even believes that a web component approach is not even the write way to do functional web development. Your two main choices if you want off the shelf web components are Bootstrap and Material Design. --- template: inverse # Material Design in Elm ??? We've been working with material design, and there was a lovely set of modules that implemented Google's Material Design Light framework. Until google abandoned that project, and the authors of the elm module decided to stop maintaining it. So now there's a new Elm library called elm-mdc which implement's googles new framework, but there's some uncertainty as to whether there's going to be another change of direction from google, so development is not going as fast as it could. This library is in beta, and although it works just fine, it's not uploaded to the elm package repository, so you have to install it manually. --- layout: true template: callout .crumb[ # Welcome # Elm # React # Elm 0.19 # SPAs # Material ] --- # Using elm-MDC .hugecode[```bash elm install elm/regex elm/svg elm/json elm/url debois/elm-dom git clone git@github.com:aforemny/elm-mdc.git make -C elm-mdc ln -s ../elm-mdc/elm-mdc.js public ln -s ../elm-mdc/material-components-web.css public $EDITOR elm.json # add elm-mdc to sourceDirectories $EDITOR public/index.html # add elm-MDC stylesheets and JS to ```] ??? Fortunately pulling in a third party module, or reusing your own private modules is pretty simple in elm. You just need to add the source directory to the module search path in your project file. That mess up there is the steps to bring in elm-mdc to a project created by create-elm-app. First we install some dependencies, which normally the package tool would handle, then we pull down the git repo, which again normally the package tool would handle. Finally we add in the stylesheet and javascript includes needed, which you would have had to do anyway. And then we're off. If you've used the very nice material ui components in React, you'll be right at home with these. --- # Lets get material .code[```elm import Material import Material.Drawer.Permanent as Drawer import Material.List as Lists type alias Model = { mdc : Material.Model Msg , -- rest of model here type Msg = Mdc (Material.Msg Msg) | LinkClicked Browser.UrlRequest | UrlChanged Url.Url , -- rest of application messages here subscriptions : Model -> Sub Msg subscriptions model = Material.subscriptions Mdc model update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Mdc msg_ -> Material.update Mdc msg_ model -- rest of update logic here ```] ??? So now, what does our app look like after we materialize it. The material design component set has its own model under the hood and generates and handles its own messages. Fortunately that's almost entirely opaque to us, all we have to do is add one component each to our model, our message type, our event subscriptions and our update function. --- ![](sidebar.png) ## From a view to a div .code[```elm view : Model -> Browser.Document Msg view model = { title = "MDC Example" , body = [ Drawer.view Mdc "drawer" model.mdc [ Drawer.header [ ] [ styled h3 [ Drawer.title ] [ text "AV Matrix" ] , styled h6 [ Drawer.subTitle ] [ text "HDMI over IP" ] ] Drawer.content [] [ Lists.nav Mdc "nav" model.mdc [] [ Lists.a [ Options.attribute (Attributes.href "/home"), Lists.activated ] [ Lists.graphicIcon [] "home", text "Home" ] , Lists.a [ Options.attribute (Attributes.href "/settings") ] [ Lists.graphicIcon [] "settings", text "Settings" ] , Lists.divider [] [] , Lists.li [] [ Lists.graphicIcon [] "keyboard_arrow_down", text "Transmitters"] -- more stuff here ]]]]} ```] ??? When we adopt the material design components our view rendering becomes more abstract, we are thinking at the component level. Just like in react there are two broad types of components, complex ones that accept or emit messages, and simple ones which don't. The complex ones have their own view function which you need to invoke, the simple ones work just the same as the atomic HTML elements we used before. But don't do what I did up there, I actually de-factored that code to be able to give you enough context on one slide to get a feel for the syntax. --- # (But don't code like that!) ### Instead... .code[```elm view : Model -> Browser.Document Msg view model = { title = "MDC Example" , body = [ styled Html.div [ css "display" "flex", css "height" "100vh" ] [ drawer model , styled Html.div [cs "page"] [ page model ] ] ] } drawer : Model -> Html Msg drawer model = Drawer.view Mdc "drawer" model.mdc [] [ drawerHeader model , drawerContent model ] ```] ??? What I'd really do is write functions that mirror the logical arrangement of my pages. This allows, for a language that isn't actually object oriented, a surprising degree of reuse. We have an inventory application for emergency services where the rendering of a vehicle manifest for shift handover checklists and the rendering of the complete inventory for the administrative staff is actually the same function, getting reused in different contexts. --- layout: true template: callout .crumb[ # Welcome # Elm # React # Elm 0.19 # SPAs # Material # Interop ] --- template: inverse # Integrating vanilla Javascript ??? So let's say you've gone the route of embedding an elm component in an otherwise javascript site, or you've done the reverse, you've written an elm application but you need to embed a complex javascript module in your page - you'll probably end up doing that if you want to use a mapping engine like Leaflet, for example. How do the two halves of your split brain website communicate? --- # Ports for crossing the veil ??? Elm's answer to this is called ports. A port is a unidirectional typed channel between elm and javascript. You can be lazy and just pass a chunk of JSON across as a string that means whatever you want, but you are better off applying more detailed typing to your ports so you sacrifice as little safety as possible. --- # Ports from JS to Elm Javascript: .code[```javascript auth = new AWSCognito.CognitoIdentityServiceProvider.CognitoAuth(authData); auth.userhandler = { onSuccess: function(result) { Elm.Main.ports.fromCognito.send({authenticated: authed, token: result.getIdToken().getJwtToken() }) } } ```] Elm: .code[```elm port fromCognito : (Session -> msg) -> Sub msg subscriptions model = Sub.batch [ Material.subscriptions Mdc model , fromCognito GotLoginState ] ```] ??? One place where I find myself passing information down the rabbit hole from Javascript to Elm is using authentication libraries. When we use Amazon cognito for user management, it looks like this. In our javascript code we set up our cognito client, and register a handler that will receive the result of a login. When that happens we grab the token which the Elm code is going to need to call our back end API and pass it down to Elm, where elm can stash it away in the model. --- # Ports from Elm to JS Elm: .code[```elm type alias MapMarker = { name: String, lat: Float, lng: Float } port addDevicestoMap: List MapMarker -> Cmd msg update msg model = case msg of FoundDevices (Ok dl) -> ( {model | devices = Just dl }, (addDevicesToMap <| List.map (\d -> {name=d.name, lat=d.lat, lng=d.lng}) dl) ) ```] Javascript: .code[```javascript app.ports.addMarkersToMap.subscribe(function(markers) { markers.forEach(loc=>{ L.marker(loc).addTo(map).bindPopup(log.name).openPopup() }) } ```] ??? As an example of going the other way, let's go back to our video distribution application. In a facility like this hotel complex where there are approximately fifty two quintillion corridors and rooms, we want to display a scrolling map of where every screen is so that we can find the blighters when they break. Our elm application fetches the location information from our backend, and we embed a Leaflet map into our DOM. We're going to need to pass some markers up the rabbit hole to populate our map. So when we receiver our API result via our FoundDevices message, we do our model update and then emit a new action that writes to a port. On the javascript side we register a callback function that runs whenever data arrives on the port. Simples. --- # Custom page elements .more[https://www.youtube.com/watch?v=tyFe9Pw6TVE] ??? While nobody was looking web browsers added the ability to define custom html elements. This is bleeding new, and only well handled in Firefox and Chrome for now, so it's not something I'd put into production yet, but Elm supports them, and this is another way to embed complex javascript components into your elm app. I haven't had a chance to play with this new feature yet myself, so I won't say too much more about it. There's a video from last year's Elm Europe that goes into some depth if you're interested. --- layout: true template: callout .crumb[ # Welcome # Elm # React # Elm 0.19 # SPAs # Material # Interop # Migrating ] --- template: inverse # Migrating to Elm 0.19 ??? Okay, the last thing I want to touch on briefly is the nitty gritty of migrating to elm 19. I took the code that I presented here at LambdaJam last year, and I followed the migration steps, which involve running the elm-upgrade tool. --- .fig80[ ![](elm-upgrade.png)] .spacedn[ # Meet elm-upgrade] ??? Elm upgrade is largely a wrapper around elm-format which applies canonical formatting to your code. Given that elm is a whitespace sensitive language, you really need a syntax aware editor to work in it comfortably, and elm-format can help your editor understand how to indent the code. But it also knows about the various versions and the functions that were removed or renamed, so you can pass elm-format over your code to repair many breaking changes. --- .code[```bash $ npm install elm@elm0.19.0 $ npm install elm-format@elm0.19.0 $ npx elm-upgrade@latest INFO: Cleaning ./elm-stuff before upgrading INFO: Converting elm-package.json -> elm.json INFO: Detected an application project (this project has no exposed modules) INFO: Switching from elm-lang/core (deprecated) to elm/core INFO: Installing latest version of elm/core It is already installed! INFO: Detected use of elm-lang/core#Json.Decode; installing elm/json I found it in your elm.json file, but in the "indirect" dependencies. Should I move it into "direct" dependencies for more general use? [Y/n]: Dependencies loaded from local cache. Dependencies ready! INFO: Switching from elm-lang/html (deprecated) to elm/html INFO: Installing latest version of elm/html Here is my plan: Add: elm/html 1.0.0 elm/virtual-dom 1.0.2 Would you like me to update your elm.json accordingly? [Y/n]: ```] ??? When you run elm-upgrade the first thing it does is migrates your configuration files, then it works throught all your dependencies. It asks permission before doing any installs, lovely polite little tool. --- .code[```bash SUCCESS! Your project's dependencies and code have been upgraded. However, your project may not yet compile due to API changes in your dependencies. See
and the documentation for your dependencies for more information. Here are some common upgrade steps that you will need to do manually: - elm/core - [ ] Replace uses of toString with String.fromInt, String.fromFloat, or Debug.toString as appropriate - elm/html - [ ] If you used Html.program*, install elm/browser and switch to Browser.element or Browser.document - [ ] If you used Html.beginnerProgram, install elm/browser and switch Browser.sandbox ```] ??? After that it goes through your code and processes any function renames or removals. In the cases where it's too hard to automatically convert it prints out some helpful instructions on what you need to do. And boom, you're up to date. --- layout: true template: callout .crumb[ # Welcome # Elm # React # Elm 0.19 # SPAs # Material # Interop # Migrating # Recap ] --- .fig30[ ![](keep-calm.jpg) ] # Recap .nolm[ * Elm is the safety of Functional Programming for the Web * You can mix Elm and React, each are learning from the other * Elm 19 is a major speedup, size bonus and library cleanup * Single Page apps are where it's at * Material Design Components are back * Talking to other JS is easy and safe(ish) * Migration to the latest version is (semi-)automated ] ??? Let's briefly recap what we've seen. We've talked about how elm is awesome, and how elm and javascript can be complimentary. We've toured the new changes in the latest version, and gone through the process of building a single page app with the create-elm-app tool. Web components are still a bit of a sticky wicket, and we looked at how to bring the elm-mdc library into your project. We've looked at how to use ports for interoperation between Elm and Javascript, and seen an example of migrating to version 19. --- # Resources, Questions ## Related talks - [http://christopher.biggs.id.au/#talks](http://christopher.biggs.id.au/#talks) ## Me - Christopher Biggs - Twitter: .blue[@accelerando_au] - Email: .blue[christopher@biggs.id.au] - Slides, and getting my advice: http://christopher.biggs.id.au/ - Accelerando Consulting - IoT, DevOps, Big Data - https://accelerando.com.au/ ??? Thanks for your time today, I'm happy to take questions in the few moments remaining and I'm here tomorrow if you want to have a longer chat. Over to you. --- # Links * Code from this talk - .fakelink[TBA] * Elm - [elm-lang.org](http://elm-lang.org/) * More [me talking about elm](http://christopher.biggs.id.au/talk/2018-05-23-functional-frontends-with-elm/) * Adventures in Elm by @jessitron, YOW 2016 - [Slides](http://yowconference.com.au/slides/yow2016/Kerr-AdventuresElm.pdf) - [Video](https://www.youtube.com/watch?v=xrmeU1JNt7s) * Elm-MDC - [aforemny.github.io/elm-mdc](https://aforemny.github.io/elm-mdc/#) * Elm-upgrade [github.com/avh4/elm-upgrade](https://github.com/avh4/elm-upgrade)