name: inverse class: center, middle, inverse layout: true .header[.floatleft[.teal[Christopher Biggs] — Elm by Example].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[lca2018] Jan 2018]] --- name: callout class: center, middle, italic, bulletul layout: true .header[.floatleft[.teal[Christopher Biggs] — Elm by Example].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[lca2018] Jan 2018]] --- layout: true template: callout .header[.floatleft[.teal[Christopher Biggs] — Elm by Example].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[lca2018] Jan 2018]] --- class: center, middle template: inverse # Elm by Example ## .green[Maps and Functions and Browsers, oh my] .bottom.right[ Christopher Biggs, .logo[Accelerando Consulting]
@unixbigot .logo[@accelerando_au] ] ??? G'day folks, I'm Christopher Biggs. --- # 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've been involved with electronic gadgets since I was a teenager, and throughout 20 years as a software engineer, architect and manager. I operate Accelerando Consulting which helps businesses use technology to reduce stress. Really, that's what I believe, that technology is the gradual process of freeing humanity from drudgery and fear, and I started Accelerando because I wanted to contribute to a future that I will be excited to live in. --- layout: true template: callout .crumb[ # Welcome # Whinging ] --- class: center, middle template: inverse # Frontend stresses me out ??? Which is not to say that I'm a style maven. Like many highly technical people elegance does not come naturally to me. Today I want to talk about how those of us who do not live and breathe UX can build pleasant user interfaces with a minimum of fuss, without having to learn a billion javascript tools and frameworks, yet still have access to some very sophisticated and stylish interface elements. --- # I need to visualise complex data ## But I'm from Mars, and front-end toolchains are from Venus ??? My desire to tell you this comes from pragmatism. I started working life as a C coder. You'd think that means I'd be safe from having to write web user interfaces. Not so much. I spent a total of nearly a nearly ten years doing web interfaces in C, in two different jobs, in two different decades. It wasn't pretty. The webpages weren't pretty either. --- # Also, When I first saw Comic Sans, I liked it ### (True story, I have questionable aesthetic judgement) ??? Now I'm not saying that it's impossible to write good looking web pages in C. But I sure didn't. As soon as I could I convinced both those workplaces to move to a more appropriate platform. So in one case we wrote a Java applet shortly before the time it became apparrent that that was a really bad idea, and then second time around we moved to PHP right around the time that the twin meteors of cloud computing and sophisticated javascript frameworks brought the curtain down the era of templated based server side rendering. --- # Can't I just be productive? ## I want a toolchain to help me, not confuse me ??? So now Javascript is where it's at. I've said before that Javascript isn't the language anyone would have chosen to become the one true universal platform, but it's the one we got. The good news is the additions to javascript in the last 5 years have rendered it quite pleasant. But the tools. Huge dependency graphs. Version conflicts. Complicated build processes. Packaging systems that would make Escher dizzy. --- # I heard about Elm ## Full props to @jessitron et.al. ??? A couple of years ago I heard about Elm. A nice simple functional language that compiles to Javascript, and has a friendly toolchain that does its best to help you out with comprehensible error messages. Yeah, right I thought. Nice toy language. I saw a presentation at YOW in 2016 from Jessica Kerr, where she built a simple user interface in the course of a 40 minute talk. It looked kinda fun, so I put it on my someday maybe list. --- # Then I gave it a try ??? Last year I consulted on a project involving indoor location data. Picture the Marauder's Map from Harry Potter. I wanted to do a throw away proof of concept to confirm that the data I was working with made sense, so I took an opportunity to try out Elm. --- # I am not a functional zealot ## But I've **seen** things, folks. ??? This is not that talk where I tell you that the clouds parted and a great beam of light shone upon the land, rainbows formed, unicorns frolicked, and programmers everywhere forgot how to swear. This was my first pure functional language. Nothing made sense for about a week. There are easier languages to learn, even though Elm's documentation, tutorials and books are excellent. But I've seen things in journey through the javascript revolution, folks. Class hierarchies where it's hunt-the-code-beneath-the-wrappers. Abstraction layer cakes so deep you need scuba gear. Code that prioritises testable over comprehensible or fixable. --- # FURB ## The "Functional Unicorns-and-Rainbows Reality Bubble" ??? When people set out to create a tool that rights all the wrongs in the world and brings programmer Nirvana, I find you often get a tool that is beautiful, but too beautiful to persist in the real world. If you want to live in shangri la, you're told that you can never have any contact with the outside world. --- # Isn't there a middle ground? ## Clean, Pragmatic, Extensible. ??? So what would a tool look like that that lets you do real work, but at least smells a little bit of unicorn. I'm going to do you the credit of presuming that you've at least heard of reactive programming. A lot of our problems as programmers stem from unexpected change. Safe use of multithreading is hard. Handling exceptions and pointer types is hard. Variable aliasing and uninitialised values are a pain. So Elm's goal is to contain the danger safely. And by danger I mean you, the carbon impurity at the front of the keyboard. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm ] --- # This talk is **not** an introduction to Elm. ## But, for those who came late... a precis * Model * Update * View That's it*. .footnote[sorta] ??? You have exactly one variable, your model. Events from the outside world change your model, and the model reacts to those changes. 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, and then the language runtime efficiently updates the document object to render the new view. ---  .spacedown[ # First iteration ## A model, a way to change it, a way to see it ] ??? So back to that rapid prototype. We have a building and a bunch of floor plans, and we want to show the locations of people and machines. Really we're only tracking the machines, but we're taking the short-cut of presuming that a many of those machines, the ones we call telephones, are suckered on to people much of the time. --- # A model ## The only variable in your entire program .code[ ```elm type alias Model = { floors : List Floor , current : String } type alias Floor = { floor_id : String , name : String , plan_url : String } initialModel = ( Model [] "select a floor", Cmd.none ) main = Html.program { init = initialModel , view = view , update = update } ``` ] ??? Now Elm really encourages you to refactor constantly, and to start with the simplest possible model that expresses your problem. This is a really close match to how I work in any case, with a series of exploratory prototypes, so I find this approach rather pleasant. Sorry about the commas, you'll have to get used to those. Elm folks like to line things up like this, and while it's not essential, it sort of makes sense after a while. This is my model at the end of day two. Day one was even simpler. I have a building. It has floors, floors have maps. We expect the user to select one particular floor. We start out with no floor selected, so we use a little prompt in place of the name. That's cheating, but I'll come back to that. --- # An update function ## Receive message, alter model, emit response .code[ ```elm type Msg = GetFloors | GotFloors (Result Http.Error (List Floor)) update msg model = case msg of GetFloors -> (model, getFloors) GotFloors (Ok fl) -> ({model | floors = fl} , Cmd.none) GotFloors (Err _) -> (model, Cmd.none) ``` ] ??? Of course our model needs to change. Our functional programs consist of inputs and outputs, otherwise they're just constants. In elm we define in advance what inputs we will process. We do this by defining one type called message, which is the union of all the things we know how to process. The elm runtime calls our update function with a message whenever it has input for us. Our update function is typically going to use that input to modify the model in some way, and possibly return a command, which is a way to send messages to the outside world. We don't need to send a command to render our view, the elm runtime implicitly invokes the view function any time the model changes. --- ## A View: Render a representation of the model in browser .code[ ```elm view model = Html.main_ [] [ Html.h1 [] [Html.text "Building Map"] , Html.div [Html.Attributes.class "sidebar"] [floor_summaries model] , Html.div [Html.Attributes.class "mainpanel"] [floor_detail model] ] floor_summaries model = Html.ul [] (List.map floor_summary model.floors) floor_summary floor = Html.li [] [Html.text floor.name] floor_detail model = Html.div [ Html.Attributes.class "floor" ] [ Html.h2 [] [Html.text model.current] , Html.canvas [] [] , Html.h3 [] [Html.text (model.floor ++ " details")] ] ``` ] ??? Our view function is responsible for presenting our model to the browser as HTML. We don't have to care about which part of the screen needs to be updated, we simply render everthing into a virtual document, and the runtime takes care of working out what is different. This sounds insane, but I've not had any performance problems. I realise that if you don't already know about elm I'm going too fast for you, I'm not trying to teach you elm here, just take a lap around the block. If you want to learn Elm, I recommend you watch Jessitron's video, which I will link to at the end. --- ## Oh yeah, those pesky side-effects .code[ ```elm getFloors = Http.send GotFloors Http.request { url = "http://localhost:8080/floor" -- * see footnote , method = "GET" , headers = [Http.header "Accept" "application/json"] , body = Http.emptyBody , expect = Http.expectJson decodeFloorSummary } decodeFloorSummary = list (map3 FloorSummary (field "floor_id" string) (field "name" string) (field "plan_url" string)) ``` ] .footnote[we'll come back to this hack] ??? Now before I said that our model update can return a command along with the new model. This is the way, besides the view, that we can affect the outside world. A common example is as here where we're making an API call to retrieve our maps. We're invoking a HTTP request and passing two critical pieces of information. First a response parse, which is the expectJson function plus a description of the expected data. Secondly, that Got Floors argument there is the type of the message. So what happens here is the elm runtime constructs a http request, sends it, passes the result to a parser, and then passes the output of the parser as a message to our update function. --- # Error handling is a type switch .code[```elm type Msg = SelectFloor String | GetFloors | GotFloors (Result Http.Error (List Floor)) update msg model = case msg of GotFloors (Ok fl) -> ({model | floors = fl} , Cmd.none) GotFloors (Err _) -> (model, Cmd.none) ```] ??? One last detail on updates, of course interactions with the outside world could result in errors. When this is possible we declare or response message as a union type, typically our expected result along with some kind of error. The benefit of this is that the compiler will ensure that we are dealing with all the possible cases. This is part of Elm's boast that you will never see a runtime error. It's not bulletproof but its damn close. Of course sometimes you are reduced begging the compiler for mercy. --- # Types ## Sloppy coding isn't just a bad idea, it's against the law. .code[ ```elm init = ( Model [] "select a floor" , getFloors ) ``` ] ^ I cheated ??? This use of types as a software quality enforcement permeates elm. I told you that I cheated earlier when I made the default floor selection a prompt to select a floor. I had to put something, because I declared that field in the model as a string, so it MUST contain a string. --- # You can't do this .code[ ```elm init = ( Model [] Nothing , getFloors ) ``` ] ^ Nothing (Elm-speak for 'null') is not a String ??? If I tried to put a null in there, it wouldn't compile. Empty string would work but that's even more cheaty. --- # But Maybe* you'll like this .code[ ```elm type alias Model = { floors : Maybe List Floor , current : Maybe String } init = ( Model Nothing Nothing, getFloors ) ``` ] ^ Type "Maybe X" means "union (Nothing, X)" .footnote[See what I did, there?] ??? The way you express optional data is with a union consisting of null and something else. This is so common that there's a special mechanism for it called the Maybe type. A Maybe is something that can either be null (or Nothing in elm-speak) or Just a value. --- # If I access a value, I must handle *all* its possible types ## "No run-time exceptions, ever*" .code[ ```elm view model = Html.main_ [] [ Html.h1 [] [Html.text "Building Map"] , case model.current of Nothing -> Html.h2 [] ["Select a floor"] Just f -> Html.h2 [] ["Level " ++ f] ] ``` ] ^ that **case** expression must handle every possible type of the value .footnote[or double your money back] ??? Whenever you are dealing with any maybe type, the compiler makes you explicitly say what happens when you get nothing. Once again this can be infuriating, but it becomes second nature after not very long. So in our mapping example, the way to do that prompt is to have a type switch, if there is no current selection we show a prompt, and if there is, we show what is selected. Our switch lets us declare a new name for the safety checked value, so we can't ever accidentally refer to the maybe null value. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Flags ] --- # Interlude: Flags You might have noticed I did this: .code[ ```elm getFloors = Http.send GotFloors Http.request { url = "http://localhost:8080/floor" ```] ??? Before we move on to the meat of this talk, I want to come back to another one of the nasty cheats I snuck past you. --- .fig50[ ] .spacedown.smallcode[ ```elm Http.request { url = "http://localhost:8080/floor" -- etc } ``` ]] ??? Did anyone catch me hard coding the url of my API? That's not going to fly in production. But I wouldn't want to hard code a production url either. Typically in web programming your webserver passes this kind of infomation in as environment or template variables. --- # Accepting flags .code[```elm type alias Flags = { apiRoot: String } type alias Model = { flags : Flags , floors : Maybe List Floor , current : Maybe String } main = Html.programWithFlags { init = initialModel , update = update , view = view } initialModel flags = ( Model flags Nothing Nothing, Cmd.none ) ```] ??? Elm has a mechanism for this too, which it calls flags. And once again, they're strongly typed. When you declare a model, you can declare that it accepts flags, and what the names and types of the flags are. --- # Passing flags .code[```html
```] ??? Then your build process or your webserver arranges to pass those flags to elm when the runtime is initialised. Here I'm using an embedded webserver written in Go, so I'm using Go's templating language to set my API root. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Flags # Ports ] --- # But, wait, what about reality ## Some of us have work to get done, hipster! ??? So over the course of a week or so my little user interface grows from a menu and a map, to a complete application with dialogs and toolbars and icons and searching and hooks into the cloud provider's authentication system. But I had help. Elm actually has about 900 communitiy packages containing libraries to do everything from video games to a complete implemtation of the google material design toolkit, which you may recognis from sites such as Google maps and gMail. --- # Accepting user input ### (Filthy little non-functional Hobbitses) .code[```elm type Msg = SelectFloor String | GetFloors | GotFloors (Result Http.Error (List Floor)) update msg model = case msg of SelectFloor f -> ({model | current = Just f}, getFloorDetails f) GetFloors -> (model, getFloors) -- etc. ```] ??? Elm's modules are another way of keeping the risky parts of your program as small as possible. And this includes user input. A web programming language that couldn't respond to input would be fairly useless. The way you accept input is by defining a message type that encodes the information that you want. --- # Enabling user input ### Page elements can emit messages (think 'onClick()' etc) .smallcode[```elm view model = Html.main_ [] [ Html.div [ Html.Attributes.class "header"] [ Html.h1 [] [Html.text "Building Map"] ] , floor_summaries model , floor_details model ] floor_summaries model = Html.div [ Html.Attributes.class "sidebar" ] [ Html.h2 [] [ Html.text "Floors" ] , Html.p [] ( List.map floor_summary (Maybe.WithDefault [] model.floors) ) ] floor_summary floor = Html.button [ Html.Events.onClick (SelectFloor floor.floor_id) ] [ Html.text floor.name ] ```] ??? Now in our view we can declare user elements that emit messages. Here we we have a menu button that sends a message with the name of the selection. That's going to arrive in our update function and alter our state. --- # Accepting input from non-user events ### Elm corrals 'side-effects' as tightly as possible * Libraries can encapsulate external events * Your main program subscribes to the ones it needs .code[```elm subscriptions model = Sub.batch [ Window.resizes GotWindowSize , Time.every (60 * Time.second) UpdateClock ] main = Html.program { init = init , view = view , update = update , subscriptions = subscriptions } ```] ??? There are other kinds of inputs besides users of course. The mechanism for these is called subscriptions, you name a subsription that you want to receive, and pass the message that it will send. Then these system inputs such as window changes or websocket input or timer ticks arrives at your update function just like everything else. So that's a lightning tour of the elm architecture. --- # I need to womble, but there's no elm-womble ??? Now, I told you that story, to tell you this one. Sooner or later you will want to do something for which there is no Elm library. You have two choices. You can write an elm library. Or you can adopt a non-elm library and punch a hole in the walls of reality to let your functional reality denial bubble interact with the great unwashed mugglespace. Elm, being pragmatic, provides a way to do this as safely as possible. --- # Ports - A telegraph line to Shangri La ## Elm's admission that there is a world outside the **FURB** * The elm universe sends messages across the Veil * The code on the Other Side sends messages back ??? Elm defines a mechanism called ports, which are another kind of subscription and message. But instead of the interaction being between your elm code and the elm runtime, you have a one way typed message to or from the Javascript runtime. If you've programmed in go, it's very similar to Go's channels. Ports let us interact with any piece of non-elm code that we want to use, but give the best effort to ensure safe values. We can't protect against outside libraries doing crazy stuff, but we can make sure that if things go splatter, we don't get any on us. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Extending # Enter Leaflet ] --- # A pivot ### Today building maps, tomorrow THE WORLD!!!* .footnote[Bwhahahahahahaha] ??? With the inexplicable xenophobia afflicting the United Kingdom, it has come to the attention of the authorities that there is an entirely undocumented population of Wombles inhabiting the UK. What is worse, it is believed they must be immigrants given the large number of eastern european names used by these creatures. So the opportunity arose to implement a global womble registration and tracking system. It seems no matter how fast i run evil catches up with me. --- .fig60[ ] .spacedown[ # Let's get concrete. ## Here* be Wombles. .footnote[No, that's not Wimbledon Common. Did you even read the books?.] ] ??? So, now Accelerando has accepted a commission to expand our marauders map from a building, to a whole country. Oh, and this isn't shangri la, so people can leave the country and maybe we want to know where in the world they are because we're creepy and have boundary issues. Along with some remedial ethics training I'm gonna need some kickass grade mapping technology. It'll probably be super hard to learn and cost a brazillion dollars. --- .fig60[ ] .spacedown[ # Leaflet.js ## Yay, Christopher gets to the point ] ??? So how much code do I need to display a global scrollable and zoomable street map, and place markers and text representing moving objects. Well, about that much. Oh and it's free. But this is a plain Javascript library, and I want to talk to it from inside my unicorn bubble. --- # Aside: Mapping 101 * Coordinates * Tiles, and tile services * Layers * Points * Popup objects * Polylines and Routes .footnote[I really am doing live mapping with Elm, but, OK I made up the Womble part] --- # Ports, the quick way ## (Inside the bubble) .code[ ```elm port module Update port toLeaflet : String -> Cmd msg update msg model = case msg of LoginSucceeded -> ( {model | authenticated = True}, (toLeaflet "{\"Init\":\"map\"}") ) ``` ] ??? So here is about the simplest possible way to send information outside the bubble. We defined a port, that carries strings, and we sent a string, that happens to be a bit of JSON out through the port, where it will be received by the javascript engine. --- # Ports, the quick way ## (Outside the bubble) .code[ ```js var app = Elm.Main.embed(document.getElementById("elmApp")); var map; app.ports.toLeaflet.subscribe(function(msg) { cmd = JSON.parse(msg); if (cmd.hasOwnProperty('Init')) { map = L.map(cmd.Init).setView([0, -100], 1); L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map); } if (cmd.hasOwnProperty('FlyTo')) { map.flyTo(cmd.FlyTo, 13, {animate: true, duration: 4}) } }); ``` ] ??? In our javascript code, we supply a callback for each channel, and we have some confidence that only expected types will appear. --- # What's wrong with that ## The types, all the beautiful types ??? The drawback of doing what I did here is that I'm just using strings --- # Back the other way ## (Inside the bubble) .code[ ```elm port fromLeaflet : (String -> msg) -> Sub msg subscriptions model = Sub.batch [ Window.resizes GotWindowSize , Material.subscriptions Mdl model , Layout.subs Mdl model.mdl , fromLeaflet FromLeaflet ] ``` ] --- # Back the other way ## (Outside the bubble) .code[ ```elm app.ports.toLeaflet.subscribe(function(msg) { cmd = JSON.parse(msg); if (cmd.hasOwnProperty('Init')) { map = L.map(cmd.Init) app.ports.fromLeaflet.send("Ack Init"); } ``` ] --- # Once more, with finess ## The more typed your channel, the safer .code[ ```elm port loginStateNotify : (Bool -> msg) -> Sub msg port layerStateNotify : ((String, Bool) -> msg) -> Sub msg port layerSizeNotify : ((String, Int) -> msg) -> Sub msg port zonePopulationNotify : ((String, Int) -> msg) -> Sub msg port fromLeaflet : (String -> msg) -> Sub msg ``` ] --- .fig70[ ] .spacedn[ # Final demo Elm fetches population data from an API server, renders a user interface, and animates map transitions to selected Wombles. In 150 lines of Elm and 30 lines of Javascript. ] --- ## Distribution is one HTML file, one JavaScript, one CSS. # 65 kilobytes* .footnote[zipped, 368k plain] --- .fig30[  ] # Recap .nolm[ * Frontend framework overload * Functional reactive programming * Aside: Basic principles of mapping * Leaflet -- Up and mapping in sixty seconds * Ports -- Typed channels through the bubble wall ] --- # 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 all week if you want to have a longer chat. Over to you. --- # Links * Elm - [elm-lang.org](http://elm-lang.org/) * Leaflet - [leafletjs.com](http://leafletjs.com) * Adventures in Elm by @jessitron - [Slides](http://yowconference.com.au/slides/yow2016/Kerr-AdventuresElm.pdf) - [Video](https://www.youtube.com/watch?v=xrmeU1JNt7s) * Elm-MDL - A beautiful and simple UI framework - [demo](https://debois.github.io/elm-mdl/) - [doco](http://package.elm-lang.org/packages/debois/elm-mdl/latest)