DEV Community

Cover image for Trying your luck with Elm
Zenobio
Zenobio

Posted on • Updated on

Trying your luck with Elm

Contents


Some time ago I was going through interviews for multiple companies and as pretty much every developer, I've been through some weird but also some funny and thoughtful ones.

This one was on the thoughtful side and gave me some considerations while also confirmed thoughts I had from a time where I was the one interviewing.

The test was really simple;

"Implement a web app that rolls a dice with a button click and show the face with the correct number to the user."

As you can see, nothing that complex but it was funny to implement in Elm and I thought it might be good to share it for the sake of describing step by step my implementation for anyone who's trying to grasp a little bit more about the language.

Boilerplate I guess...

The first thing would be prepare the boilerplate. I won't assume you have any previous experience with Elm, will try my best to explain my thought process here from the ground up, let's go from the most basic steps then.

module Main exposing (..)

import Browser
import Html exposing (..)
Import Random

-- MAIN


main =
    Browser.element
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }

-- MODEL


type alias Model = {}


init : () -> ( Model, Cmd Msg )
init _ =
    ( Model, Cmd.none )

-- UPDATE


type Msg = NoOp


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NoOp ->
            (model, Cmd.none)

-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none


view : Model -> Html Msg
view _ =
    div [] [text "Hello World"]

Enter fullscreen mode Exit fullscreen mode

Dissecting the boilerplate we have a main as the entry point that calls the init function with a Model that is just a empty Record that will be passed to the view function which will only render a div element with a Hello World text as its content.

Baby Steps and Generators

Now that we have the boilerplate let's continue with baby steps.

  • How will we think about implementing this "dice face randomizer"?.

Well, I tend to think towards
Behavior -> Visualization | UI -> Polishing and I'll be following those steps.

  • But what can we consider as behavior?

I see "generate a random number within a button click" as the basic behavior of this problem. We can implement this with a Generator.

In Elm you cannot call a Math.random the same way you can call in Javascript. It is a non deterministic behavior and as such it should be correctly handled by Elm in a slightly different way to achieve what we want. Taking a look on the documentation for the Random module it's possible to see the example below that encapsulates pretty much what we need to do.

type Msg = NewNumber Int

newNumber : Cmd Msg
newNumber =
  Random.generate NewNumber oneToTen
Enter fullscreen mode Exit fullscreen mode
  • But what does this example says exactly?.

It says that you can call a Random.generate with two arguments, a function that returns a Msg type and a Generator of some type so it can return a Cmd Msg for you. Regarding the type annotations and the basics of how it works I think the documentation can explain it better than I'll ever be able to so please take a look for more in depth details.

We know from the documentation that Random.generate can create a Cmd Msg for us but we don't have anything useful for that in our Msg type yet, so, lets implement it to receive a randomized number:

type Msg =
  Rolled Int

oneToSix : Random.Generator Int
oneToSix = Random.int 1 6
Enter fullscreen mode Exit fullscreen mode

Now we have a useful Msg but that's not enough, as you might remember the Random.Generator we have here is not something we can attach to our Msg type. It will not work since Rolled receives an Int and oneToSix is of type Random.Generator Int.

This oneToSix generator need to be called to really "generate" the value. That's a side effect and for that we need to ask Elm to perform it for us. So, now we need to use the Random.generate to generate a Cmd that Elm will gladly perform.

rollDice : Cmd Msg
rollDice = Random.generate Rolled oneToSix
Enter fullscreen mode Exit fullscreen mode

That's nice, now we have a Cmd that Elm can perform and pass the value to our Msg type which will gladly update our number. Update? Do we have this update logic in place? I don't think so...

init : () -> ( Model, Cmd Msg )
init _ =
    ( Model 1, Cmd.none )

...

type alias Model = { value : Int }

...

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Rolled newValue ->
            ({ model | value = newValue }, Cmd.none)
Enter fullscreen mode Exit fullscreen mode
  • Now I think we're covered. Our init function was updated to initialize the Model with a default value of 1, the Model is holding a value to be rendered and the update function is receiving the newValue which will be generated by the rollDice command right?

Unfortunately not. Since rollDice is a command we need a way to trigger that and say "Hey Elm, perform this action for me".

If we look closely to the update function we can see that it returns a tuple with ( Model, Cmd Msg ), the type signature for our rollDice is a Cmd Msg.

  • Why not put this here on our update function since that's the way Elm transitioning from one model state to the other? Should I put that on the Rolled scope?

Put the rollDice command in the returned tuple from the Rolled Msg will trigger, every time this message have been triggered, the rollDice command. But rollDice calls Rolled and it will create a loop that freezes your application.

The correct way is to create a second Msg that will say "Hey Elm, call rollDice to me". That way the Random.generate can call the Rolled without creating a loop within our update.


type Msg
  = Rolled Int
  | NewNumber

...

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Rolled newValue ->
            ({ model | value = newValue }, Cmd.none)

        NewNumber ->
            (model, rollDice)
Enter fullscreen mode Exit fullscreen mode

Now the only missing piece is to be able to click a button to generate this number and see the number on the screen.

view : Model -> Html Msg
view { value } =
    div []
      [ span [] [ text (String.fromInt value) ]
      , br [] []
      , button [ onClick NewNumber ] [ text "Randomize" ]
      ]
Enter fullscreen mode Exit fullscreen mode

Here we need to use String.fromInt since the type of the value variable is Int and the Html.text function receives a String.

You can click here to check the app till this point.

Look at my dice, my dice is amazing!

Now that we have our generator in place and we can see that it is working, it's time to start to move from the abstract behavior to the more concrete idea of a dice.

A dice face is made of dots and spaces between those dots.

That's not so helpful isn't it? We need a way to check how those dots are distributed for each one of the its faces. Just like this next picture.

Dice faces

Now that's helpful. We can see that the dots have a distribution that is possible to be described as lines and columns. The best way to visualize that is as if we had a Rubik's cube where every single face is white and we can paint black some of the pieces that compose each face to describe one of the dice faces.

Thats a good metaphor but you have to understand that it is mostly a description of a 3x3 matrix (Same as each single face of the Rubik's cube) and following that we will implement a matrix representation using lists to render our amazing dice.

renderDot : Bool -> Html Msg
renderDot visible =
    let
        dotClass = 
          if visible then
              "dot"
          else
              "dot__invisible"
    in
    div [ class dotClass ] []

view : Model -> Html Msg
view { value } =
    let
        renderRow lst =
            lst
                |> List.map renderDot
                |> div [ class "dots-row" ]

        rows =
            [ [ True, False, True ]
            , [ True, False, True ]
            , [ True, False, True ]
            ]
                |> List.map renderRow

        renderDice _ =
            div [ class "dice-container" ]
                [ rows |> div [ class "dice" ]
                ]
    in
    div [ class "main" ]
        [ renderDice value
        , div [ class "button-container" ] [ button [ onClick NewNumber ] [ text "Roll The Dice" ] ]
        ]
Enter fullscreen mode Exit fullscreen mode

Here we're creating a really simple renderDot function that receives a boolean flag and if its True the dot will be rendered as a black spot otherwise it will be rendered as transparent so we can have the correct spacing between dots.

The view function got a little more weird but isn't that complex. We have a main div and inside that we have two containers. First one holding the renderDice function that returns a Html Msg and thus a valid children. The second container holds the button to roll the dice.

Within the let...in scope we have:

  • renderDice: A function that receives an argument but currently ignores it and return a Html Msg, our dice-container with the dice as its direct children.
  • rows: A value that calls renderRow passing a representation of a 3x3 matrix.
  • renderRow: A function that receives a list of boolean flag and maps every element to our renderDot function creating at the end a list of Html Msg. In that case a row containing a representation of three dots.

You can check the current state of the implementation and the whole CSS necessary to render the UI here. Please take your time to understand if needed.

Roll the Dice

The missing piece right now is connect our current functions in a way that we can check which number renders which face.

Lets firstly check what we already have that could be useful so we don't get lost within multiple moving parts.

Do you recall our renderRow function and our rows value? Let's move then out of the let...in scope so we can re-use them to build the rows automatically for us.

generateDotsFromMatrix : List (List Bool) -> Html Msg
generateDotsFromMatrix matrix =
    let
        generateDotsRow list =
            list
                |> List.map generateDot
                |> div [ class "dots-row" ]
    in
    matrix
        |> List.map generateDotsLine
        |> div [ class "dice__content" ]
Enter fullscreen mode Exit fullscreen mode

Now we have changed our previous rows value to a generateDotsFromMatrix, a function that receives our matrix representation and returns the content of the dice. We also renamed the renderRow function to generateDotsRow which describes what the function does in a more clear way and renamed renderDot function to generateDot for the same reason.

That's it, only a matter of grounding some moving parts to ease our transition to a more reusable logic.

  • What about the other faces? This dice only renders one face...

You're right, let's fix that.

renderDice value =
    let
        dots =
            case value of
                1 ->
                    generateTruesFromMatrix 
                        [ [ False, False, False ]
                        , [ False, True, False ]
                        , [ False, False, False ]
                        ]

                2 ->
                    generateTruesFromMatrix 
                        [ [True, False, False]
                        , [ False, False, False ]
                        , [ False, False, True ]
                        ]

                3 ->
                    generateTruesFromMatrix
                        [ [ True, False, False ]
                        , [ False, True, False ]
                        , [ False, False, True ]
                        ]

                4 ->
                    generateTruesFromMatrix 
                        [ [ True, False, True ]
                        , [ False, False, False ]
                        , [ True, False, True ]
                        ]

                5 ->
                    generateTruesFromMatrix
                        [ [ True, False, True ]
                        , [ False, True, False ]
                        , [ True, False, True ]
                        ]

                _ ->
                    generateTruesFromMatrix 
                        [ [ True, False, True ]
                        , [ True, False, True ]
                        , [ True, False, True ]
                        ]
    in
    div [ class "dice-container" ]
        [ div [ class "dice" ] [ dots ]
        ]


view : Model -> Html Msg
view { value } =
    div [ class "main" ]
        [ renderDice value
        , div [ class "button-container" ] [ button [ onClick NewNumber ] [ text "Roll The Dice" ] ]
        ]
Enter fullscreen mode Exit fullscreen mode

Updating our function renderDice, using pattern match to map from all the possible given values to the correct matrix that represents the combination of black and transparent dots for a given dice face.

  • That's it, right, a fully fledged "Roll the dice" app!!.

Yeah, if you only consider what was asked for, that's it.

But what if, for some reason, someone touches your renderDice function and changed some flags. Will be hard to track those changes just by looking at it right?. A flag doesn't mean anything by itself we can just change the renderDot function to do the opposite and voila all the app logic breaks and we will not understand easily because only the if...else was swapped and that's why using those boolean flags isn't a good idea. It's time to do some changes in order to keep us sane if we ever need to touch this code again.

type Dot 
  = Dot
  | Placeholder 

...

generateDot : Dot -> Html Msg
generateDot mDot =
    let
        cls =
            case mDot of
                Dot ->
                    "dot"
                Placeholder ->
                    "dot__invisible"
    in
    div [ class cls ] []

generateDotsFromMatrix : List (List Dot) -> Html Msg
-- Implementation remains untouched...
Enter fullscreen mode Exit fullscreen mode

Created a Dot custom type that better fits the domain of the problem and did small updates in the generateDot function and in the type annotation of the generateDotsFromMatrix function.

The last change is at our renderDice function, we'll change every occurrence of True to Dot and False to Placeholder that way we can easily understand if some of the faces have something weird going on later.

That's a small change but with a big impact on how we handle our code since using this custom type helps on understand what is being rendered for each part of the face representation and catch any weird logic change.

-- Easy, the matrix that corresponds to 2.
[ [Dot, Placeholder, Placeholder]
, [ Placeholder, Placeholder, Placeholder ]
, [ Placeholder, Placeholder, Dot ] 
]
Enter fullscreen mode Exit fullscreen mode

I know that everyone who's worked with Elm at a higher degree than pet projects will say that custom type should be considered and implemented from the start but I'm showing how my thought process was during this small challenge which had a specific time to finish while also trying to keep it easy to digest for people who are not really used to code with the language.

Here's the Full App.

Final Considerations

The group of people whose interviewed me was really thoughtful and easy going. They not only gave me the whole boilerplate to start with but also let me consult the documentation and even Stack Overflow if I wanted to. That's something that most technical interviews, at least that I've been through, tend to prevent you from doing.

But isn't that what we do, daily, as programmers? Check documentation, search for syntax specifics and sometimes understand how to do something through the experience of others. It should not be taken away just to see if you can hold a bunch of information that can be easily searched throughout internet. Our job should be based on problem solving not to try and hold a ginormous amount of information in our heads.

Throughout the interview the whole group was also really supportive and interactive, helping me not wander so much with the implementation details so I wouldn't throw curve balls to handle later. That's also something that a number of technical interviewers tend not to do. They see themselves as spectators, not interviewers. Pretend that they can't help you because if they do its like "cheating" but that's not the case and it's really good to see that whoever is interviewing you isn't just a static judge but a fellow developer who's capable of discuss and help you to build a better mindset for the problem while also preventing you to go down some unnecessary rabbit holes. That's what's expected from a good pair programming and what I think most interviewees want from a technical interview. They also want to be hired but that's a whole different conversation...

My final consideration is that you don't need complex problems to access someone's expertise and if you're an interviewer, you don't need to be a static judge. Strive to be someone that answer questions, pair programming and help the interviewee to feel heard and ease their concerns. I bet you can learn a bunch from them and vice-versa. Just be a fellow developer and enjoy the possibility to get to know a bunch of other developers with different backgrounds and experiences.


TLDR

A pretty simple but funny technical interview challenge with minor details that enable the interviewers to grasp the experience level of the interviewee with the language.
Full implementation

Latest comments (0)