A while ago released an for anyone to host their own personal radar. ThoughtWorks open source repository technology I was curious to see how the reputable company implemented it and strangely I noticed a lot of concepts that reminded me of Java, such as and error handling with . stateful objects try/catch/exception All the application really does is take a single input and produce a tech radar. A Java-esque approach seemed inappropriate for that use case so I decided to rewrite some of it in ! Elm Step 1: Model the radar Let’s start things off by thinking about how to model the radar itself. If we model our problem carefully it can simplify the work ahead of us. First let’s consider what we know about the data. The radar is split into and . These quadrants and rings represent different categories that several can be placed into. four quadrants four rings blips Since we know there are only four quadrants and rings let’s represent those as . union types So far so good. Next up, the blips. Modeling the blips requires some extra thinking because there are two representations of a blip. The first representation is the blip data from the Google Sheet ( ). The information included is the , , , an flag, and a . example name ring quadrant isNew description The blips that are visualized on the radar need those same 5 properties but in order to be displayed on the radar they also need an . x-y position Several ways to model these blips include: This option uses fewer types but will inevitably lead to more . Throw all properties into a single record and make the type of the position field a . Maybe maybe handling This option removes the but at the cost of in two separate record fields. Treat the blips from the Google Sheet as a distinct type and record from the blips visualized on the radar. maybe verbosity duplicating data Since the properties of the radar blip are a superset of the blips from the Google Sheet we can . A blip positioned on the radar will have the Google Sheet blip data in addition to a defined position. represent the data with composition There are other options worth considering, especially options that leverage union types, but the third option will suffice for now and we can refactor later if needed. Next up is visualizing the radar. If you’re curious on how we’re doing in comparison to ThoughtWorks’ open source example then swing on over to their OO equivalent modeling . Step 2: Visualize the radar Now that we have the basic building blocks to construct a view let’s start building the radar visualization. The first thing I want to do is get the quadrants and rings visualized. We’ll heavily use SVG and borrow a lot of the styles from the ThoughtWorks open source project. I’m sure they won’t mind. Creating an arc The radar isn’t a circle. It’s technically four quarter-circles put together, a.k.a arcs. Circles in SVG are not that hard. Arcs, on the other hand, suck big time. Here’s a demonstration of what I mean. With the above code working it will be exiled to a utility module so that we don’t have to think about the implementation details for creating an arc in SVG. Visualizing quadrants Visualizing a quadrant only requires that we visualize 4 arcs (rings), each with a different radius. Not bad. If we do this three more times and provide different numbers we get the entire radar background. Visualizing blips There are two types of blips that can be visualized which is determined by the flag. One is a rounded triangle and the other resembles a smudged circle. isNew I’m going to simply transcribe ThoughtWorks’ for these blip visualizations to Elm. code ..more SVG implementation details that we’re not going to look at ever again. Next up, visualizing mocked blips on the radar. Step 3: Wire up the view with mock data Before we consume actual data from a Google Sheet we’re going to mock up some data to make sure our blips are correctly displayed on the radar. The above function is but it will produce 16 blips on the radar, one in each ring in each quadrant. However, this brings up the question of where exactly to position the blips. temporary I went ahead and peeked at how . In short, they produce a sinusoidal curve that a blip is to be positioned on and if it collides with another blip it will try another random position on this curve. ThoughtWorks did it We can do the same thing in Elm but it brings up an interesting topic.. how to do randomness in a pure functional language. <quick detour> Pure randomness with generators In Elm, every function is a pure function. In order to produce randomness we can ask Elm to generate us a random value for a given seed. A random value and another seed is returned so that we can use that seed to ask for yet more random values. However, if we’re producing many random values then managing these seeds becomes extremely tedious and prone to mistakes. Fortunately, Elm gives us the concept of a . According to its documentation, Generator a generator is like a recipe for generating certain random values. Here’s an example generator that can be used to produce random integers from 3 to 7. Random.int 3 7 Here’s another generator for producing a random list of even values under 100. Simply creating a generator doesn’t actually produce a random value. It still has to be passed to . Random.step (randomEvens, nextSeed) =Random.step (generateEvenValues 10) (Random.initialSeed 123) Given this knowledge we can compose a few generators to randomly position blips along a sinusoidal curve and keep producing values if there is a collision. This will behave identically to the ThoughtWorks algorithm and it also happens to be where we convert the to . List GoogleSheetBlip List PositionedBlip Randomly positioned blips with Generators I realize we went from 0 to 60 with generators in about 90 seconds. If the above code is not immediately understandable, don’t worry. Like much with Elm, it is initially unfamiliar but it very quickly becomes second nature to read that code. </quick-detour> Blips visualized with randomness When we run the mock data at the beginning of this section into our view function we get a radar with blips on it. At this point we can keep throwing data at the radar and verify that the algorithm for determining blip positions is working. The positioning here seems reasonable. Moving on. If you’re tracking progress here’s how ThoughtWorks is doing the same . It’s also worth noting that the radar is mirrored along the y-axis because we hard coded the random seed. That’s fine. Step 4: Model the app state Just as before, modeling the app state is incredibly important. When writing your own Elm applications it’s worth pausing, discussing, and considering the trade-offs for how to model your data. For this application we already have a radar but we’re missing a page to enter a Google Sheet url. I chose to model that page state like this: It will have a form with an initially empty url. The form might have an error and we’ll want to know if we’re actively retrieving the Google Sheet. There are to this approach, but we’ll stick with these three properties for now. alternatives That covers what I’m calling the . Lastly, there’s the that needs to be provided to The Elm Architecture. Landing Page Model This is loosely based on except with a few things cut out for the sake of simplicity, such as not dealing with routes for such a small application. Richard Feldman’s SPA example It’s important to point out that at a high level this application is in one of two states: We’re on the landing page and interacting with the url input form We’re on the radar page interacting with a tech radar Step 5: User input, regex, data retrieval, and error handling The landing page involves , , , , and in all of those cases. I’m going to skip the user input part because if you made it this far you know how to do that already. receiving input from the user parsing out a Google Sheet ID performing an HTTP request parsing the response handling errors If I’m wrong and handling user input in Elm is new to you I suggest pausing and checking out this example . Regular expressions in Elm We’re about to go 0 to 60 again. Hang on. The user will supply us with a link to their published Google Sheet, such as “ ”. https://docs.google.com/spreadsheets/d/11Fd0lwNIEUs2ymxNEiTfpM5CoQHQbMuOId8TjrQHDn0/edit?usp=sharing Hidden within this URL is an ID that we need to perform an HTTP request. It’s that unreadable string between “/d/” and “/edit?”. First we’ll declare a regular expression that will find that ID. It goes without saying that credit for this regex goes to ThoughtWorks again. Within that regex are groups and we care about the first group specifically. In order to apply a regex to a string we use the function. find matches =find All sheetIdRegex url That returns a list of matches that take the following shape. Our Google Sheet ID is hidden within the list. So what we want to do is get the first match in the list of matches returned from , and on that first match find the first item in its . submatches find submatches If you’ve worked with Elm lists and before you’re probably already aware that we’re about to go head first into . Thanks to the code to pluck off that ID and produce a Result isn’t too bad. List.head MaybeLand elm-community/maybe-extra The first collapses the double Maybe that occurs when you do a on a List of Maybes. However, it’s still a Maybe returned from so the second collapses it one more time before converting it to a Result. MaybeExtra.join List.head Maybe.map MaybeExtra.join Retrieving the Google Sheet data Once we have the ID we can make a request to retrieve the Google Sheet data in CSV format. When this Cmd is performed it’ll call with the Result. httpResultToMsg The above function does the following On success, split the string by new lines Iterate over each row and attempt to parse the CSV as a blip Successfully parsing data puts it in the list while parse errors are added to the list. blips errors Send off a Msg with the blips and any errors Parsing each CSV row is as simple as attempting to map the data onto our data model defined earlier. Conclusion This post is getting long so I’ll wrap things up by making a few observations in rewriting some of the ThoughtWorks Tech Radar. The BYO radar project used for , for graphics and HTML templating, and for Google Sheets. It uses to model its data and incorporated rather than gracefully handling errors. chance randomness d3 tabletop parsing stateful object instances exception handling The code is fairly complicated and even with understanding the desired behavior it’s difficult to track the implementation details. Most of all, after spending so much time in that code I still don’t understand what much of the code does. On the other hand, a functional approach with Elm yielded more comprehensible code with similar functionality. In fact, once you grasp the way Elm does randomness and regular expressions you’ll realize the code is much more simple too. To see the partial rewrite in action check out these two links: http://ckoster22.github.io/elmradar/ https://github.com/ckoster22/techRadar/tree/blogpost