ELM : Pick random item from list

28 Nov 2018

Generating random number in ELM is tricky. We can’t call a function Math.random() like in JavaScript. ELM by design support pure functions only. If you need to do anything impure you have to use Cmd, Msg and Update means you have to use ELM runtime to the impure job. To make our life easier ELM 0.19 comes with elm/random package.

elm/random gives a Generator which ask ELM runtime to generate a random number and pass back as Update Msg. Random.generate will accept the Msg with 1 argument and a function with describe the type and range of the random.

type Msg = RandomNumber int
Random.generate RandomNumber (Random.int 0 10)

The above example is to generate random integer between 0 & 10. Since instead of returning value, Random.generate will send the message RandomNumber with random integer generated. To receive and handle this generated random integer we should have update function as below

update msg model ->
    case msg of
        RandomNumber rn ->
            -- use the random number rn

Random Item from list

Lets consider we have a list of items and we need to pick item randomly from this list.

type alias Char = 
    { text: String }

type alias Flags = 
    {  }


type alias Model =
    { selected : Maybe Char, chars: List Char }


initialModel : Model
initialModel =
    { selected = Nothing
    , chars = [
        {text= "A"}
        ,{text= "B"}
        ,{text = "C"}
        ,{text= "D"}
        ,{text= "E"}
      ] 
    }

selecting random item can be trigger in multiple ways like a button click or on app mount. For this post let pick the random item on a button click.

renderText : Maybe Char -> Html Msg
renderText selected =
    case selected of 
        Just char ->
            text char.text
        Nothing ->
            text "Please click on Random Button"


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick FindRandom ] [ text "Random" ]
        , div [] [ (renderText model.selected) ]
        ]

Now we have a button with Random text which will send Cmd FindRandom. Next lets handle the FindRandom in the update.

type Msg
    = FindRandom


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FindRandom ->
            (model, Random.generate RandomNumber (Random.int 0 (List.length model.chars - 1)))

Once the update method receive the Msg FindRandom it will trigger the Random.generate with boundary 0 & no.of item in the list. ELM runtime generate random number in the given boundary and will send the Msg RandomNumber with the generated number. Next lets handle that. The above code will become,

type Msg
    = FindRandom
    | RandomNumber Int


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FindRandom ->
            (model, Random.generate RandomNumber (Random.int 0 (List.length model.chars - 1)))
        RandomNumber rn ->
            let
                selected = Array.fromList model.chars
                    |> Array.get rn
            in
                ({ model | selected = selected }, Cmd.none)

Once the update receives RandomNumber with the generated number, we have to pick Item from list. Since we don’t have index for list we need to convert th list into array and use the generated number as index to get the List Item. Once we get the List Item we update the model.selected value and that will get rendered in the view.

The whole code will look like,

module Main exposing (main)

import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

import Random
import Array

type alias Char = 
    { text: String }

type alias Flags = 
    {  }


type alias Model =
    { selected : Maybe Char, chars: List Char }


initialModel : Model
initialModel =
    { selected = Nothing
    , chars = [
        {text= "A"}
        ,{text= "B"}
        ,{text = "C"}
        ,{text= "D"}
        ,{text= "E"}
      ] 
    }


type Msg
    = FindRandom
    | RandomNumber Int


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FindRandom ->
            (model, Random.generate RandomNumber (Random.int 0 (List.length model.chars - 1)))
        RandomNumber rn ->
            let
                selected = Array.fromList model.chars
                    |> Array.get rn
            in
                ({ model | selected = selected }, Cmd.none)

renderText : Maybe Char -> Html Msg
renderText selected =
    case selected of 
        Just char ->
            text char.text
        Nothing ->
            text "Please click on Random Button"


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick FindRandom ] [ text "Random" ]
        , div [] [ (renderText model.selected) ]
        ]
        
init : Flags -> (Model, Cmd Msg)
init flags =
    (initialModel, FindRandom)


main : Program Flags Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = always Sub.none
        }

This is an extracted version which used in random gstin app and its source is available on github.com/revathskumar/random-gstin

The Running version is available on ellie-app.

Versions of Language/packages used in this post.

| Library/Language | Version |
| ---------------- |---------|
|      ELM         |  0.19.0 |
|   elm/random     |  1.0.0  |
If you find my work helpful, You can buy me a coffee.