mmalik
mmalik

Reputation: 185

Transform List (Generator a) into Generator (List a)

Here is a simplified version of my problem: Generate a list of random values in which each consecutive value depends on the previous one.

For example, generate a list of random Int, in which each consecutive value will establish minimum for the next step. Let's assume that starting value = 0 and maximum value is always currentValue + 5 :

  1. First step: Random.int 0 5 => 3
  2. Next: Random.int 3 8 => 4
  3. Next: Random.int 4 9 => 8
  4. etc.

Here is my approach:

intGen : Int -> List (Rnd.Generator Int) -> List (Rnd.Generator Int)
intGen value list =
  if length list == 10 then
    list
  else
    let newValue = Rnd.int value (value + 5)
        newList = newValue :: list
    in intGen newValue newList

Let's transform it into Rnd.Generator (List Int):

listToGen : List (Rnd.Generator Int) -> Rnd.Generator (List Int)
listToGen list =
  foldr
    (Rnd.map2 (::))
    (Rnd.list 0 (Rnd.int 0 1))
    list

I don't like this part: (Rnd.list 0 (Rnd.int 0 1)). It generates initial value of type Rnd.Generator (List Int), in which (Rnd.int 0 1) is actually never used but is needed by type checking. I would like to skip this part somehow or replace it with something more generic. Is it possible or my implementation is erroneous?

Upvotes: 1

Views: 75

Answers (1)

Chad Gilbert
Chad Gilbert

Reputation: 36385

Here is one solution which uses andThen and map. The first parameters is the number of elements you want in the list. The second parameter is the starting value.

intGen : Int -> Int -> Rnd.Generator (List Int)
intGen num value =
    if num <= 0 then
        constant []
    else
        Rnd.int value (value + 5)
            |> Rnd.andThen (\i -> intGen (num-1) i
            |> Rnd.map (\rest -> i :: rest))

To match your example of a list of size 10 starting with 0 as the first low value, you would call this as intGen 10 0.

constant is a generator from elm-community/random-extra, or it can be defined simply like this (because it isn't exposed in the core Elm codebase):

constant : a -> Rnd.Generator a
constant a = Rnd.map (\_ -> a) (Rnd.int 0 1)

Regarding your example, I don't think you would want to use List (Rnd.Generator Int) because that implies a list of generators that aren't tied together in any way. That's why we need to use andThen to pull out the random value just generated, call intGen recursively minus one, then use map to put the list together.

Upvotes: 3

Related Questions