Search code examples
randomuuidelm

Generate random UUIDv4 with Elm


I'm trying to generate random UUID's v4 within a loop:

    randomUuid =
         -- TODO: find a way to generate random uuid for variableId

    updatedVariables =              
         group.variables |> List.map (\variable -> { variable | id = randomUuid })

I read the doc of elm/random and elm/uuid but could not find how to generate an UUID without using a seed.

The only thing I could do is:

newUuid : Random.Seed -> ( String, Random.Seed )
newUuid seed =
    seed
        |> Random.step UUID.generator
        |> Tuple.mapFirst UUID.toString

I see that elm/random as an independentSeed function but I cannot get it to generate a seed.

The node equivalent of what I'm trying to achieve with randomUuid is:

const { uuid } = require('uuidv4');

const randomUuid = uuid();

I feel like I might be missing some important concept in Elm here but cannot figure that one on my own. Any help or pointer would be greatly appreciated.


Solution

  • Generating random values is an effect and as such a pure language cannot just perform it.

    However, there is a pure version of randomness, which is using random seeds. These have the property that every time you generate a value using the same seed, you get the same value - hence this is just a pure computation and is completely ok in a pure context.

    Elm allows you to execute effects as Cmd, the thing you return from your init and update functions. So one option you have is to always return Random.generate GotANewUUID UUID.generator before you need it, then perform your computation when you handle the GotANewUUID msg.

    The other option is to keep track of the random seed. You either start with a deterministic one with Random.initialSeed (probably not what you want with UUIDs, as they would be exactly the same on every run of your program), or in your init function you return Random.generate GotNewSeed Random.independentSeed. Then you store the seed in your model. Every time you need to generate a new UUID, you use your newUuid function above, making sure to store the new seed.

    Here's an example:

    import Random
    import UUID
    
    type alias Thing = 
        { id : String
        -- , some other stuff
        }
    
    type alias Model =
        { seed : Random.Seed
        , stuff : List Thing
        }
    
    type Msg
        = GotNewSeed Random.Seed 
        | AddAThing Thing
        | AddABunchOfThings (List Thing)
    
    init : () -> (Model, Cmd Msg)
    init flags =
        ({ seed = Random.initialSeed 567876567 
        -- Let's start with a deterministic seed
        -- so you don't need to deal with Maybe Seed later
         , stuff = []
        }, Random.generate GotNewSeed Random.independentSeed
        )
    
    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
        case msg of
            GotNewSeed seed ->
                ({model | seed = seed}, Cmd.none)
        
            AddAThing thing ->
                let
                    (newId, newSeed) = 
                         newUuid model.seed
                in
                ({ model | stuff = { thing | id = newId } :: model.stuff
                 , seed = newSeed }
                , Cmd.none
                )
    
             AddABunchOfThings things ->
                 let
                     (newStuff, newSeed) =
                          List.foldl (\thing (stuff, seed) ->
                              newUuid seed
                                  |> Tuple.mapFirst (\id -> 
                                      { thing | id = id } :: stuff
                                  )
                          ) (model.stuff, model.seed) things
                  in
                  ({model | stuff = newStuff, seed = newSeed}, Cmd.none)