<h2>How not to involve IO in random number generation:</h2>

<p>This question has received excellent answers. However, it might leave some readers under the impression that pseudo-random number generation (PRNG) within Haskell is necessarily linked to IO.<br></p>

<p>Well, it's <strong>not</strong>. It is just that in Haskell, the default random number generator happens to be "hosted" in the IO type. But this is by choice, not by necessity.</p>

<p>For reference, here is a <a href="https://arxiv.org/pdf/1811.04035" rel="nofollow noreferrer">recent review paper on the subject of PRNGs</a>.
PRNGs are deterministic mathematical automata. They do not involve IO. Using PRNGs in Haskell does not need to involve the IO type. At the bottom of this answer, I provide code that solves the problem at hand without involving the IO type, except for printing the result.</p>

<p>The Haskell libraries provide functions such as <code>mkStdGen</code> that take an integer <em>seed</em> and return a pseudo-random number generator, that is an object of the <code>RandomGen</code> class, whose state is dependent on the value of seed. Note that there is nothing magic about <code>mkStdGen</code>. If for some reason you do not like it, there are alternatives, such as <a href="http://hackage.haskell.org/package/tf-random-0.5/docs/System-Random-TF.html" rel="nofollow noreferrer">mkTFGen</a> which is based on the <a href="https://www.schneier.com/academic/skein/threefish.html" rel="nofollow noreferrer">Threefish block cipher</a>.
<br></p>

<p>Now, pseudo-random number generation is not managed in the same way in imperative languages such as C++ and in Haskell. In C++, you would extract a random value like this: <code>rval = rng.nextVal();</code>. On top of just returning the value, calling nextVal() has the <em>side effect</em> of altering the state of the <code>rng</code> object, ensuring that next time it will return a different random number.</p>

<p>But in Haskell, functions have no side effects. So you need to have something like this:</p>

<pre><code>(rval, rng2) = nextVal rng1
</code></pre>

<p>That is, the evaluation function needs to <em>return</em> both the pseudo-random value and the updated state of the generator. A minor consequence is that, if the state is large (such as for the common <a href="https://en.wikipedia.org/wiki/Mersenne_Twister" rel="nofollow noreferrer">Mersenne Twister</a>  generator), Haskell might need a bit more memory than C++.</p>

<p>So, we expect that solving the problem at hand, that is randomly transforming a list of strings, will involve a function with the following type signature: <code>RandomGen tg =&gt; [String] -&gt; tg -&gt; ([String], tg)</code>.</p>

<p>For illustration purposes, let's get a generator and use it to generate a couple of "random" integers between 0 and 100. For this, we need the <code>randomR</code> function:</p>

<pre><code>$ ghci
Prelude&gt; import System.Random
Prelude System.Random&gt; :t randomR
randomR :: (RandomGen g, Random a) =&gt; (a, a) -&gt; g -&gt; (a, g)
Prelude System.Random&gt; 
Prelude System.Random&gt; let rng1 = mkStdGen 544
Prelude System.Random&gt; let (v, rng2) = randomR (0,100) rng1
Prelude System.Random&gt; v
23
Prelude System.Random&gt; let (v, rng2) = randomR (0,100) rng1
Prelude System.Random&gt; v
23
Prelude System.Random&gt; let (w, rng3) = randomR (0,100) rng2
Prelude System.Random&gt; w
61
Prelude System.Random&gt; 
</code></pre>

<p>Note that above, when we forget to feed the <em>updated</em> state of the generator, rng2, into the next computation, we get the same "random" number 23 a second time. This is a very common mistake and a very common complaint. Function <code>randomR</code> is a pure Haskell function that does not involve IO. Hence it has <em>referential transparency</em>, that is when given the same arguments, it returns the same output value.</p>

<p>A possible way to deal with this situation is to pass the updated state around manually within the source code. This is cumbersome and error prone, but can be managed. That gives this style of code:</p>

<pre><code>-- stateful map of randomize function for a list of strings:
fmapRandomize :: RandomGen tg =&gt; [String] -&gt; tg -&gt; ([String], tg)
fmapRandomize [] rng = ([], rng)
fmapRandomize(str:rest) rng = let (str1, rng1)  = randomize str rng
                                  (rest1, rng2) = fmapRandomize rest rng1
                              in  (str1:rest1, rng2)
</code></pre>

<p>Thankfully, there is a better way, which involves the <code>runRand</code> function or its <code>evalRand</code> sibling. Function <code>runRand</code> takes a <em>monadic computation</em> plus (an initial state of) a generator. It returns the pseudo-random value and the updated state of the generator. It is much easier to write the code for monadic computations than to pass the generator state manually around. </p>

<p>This is a possible way to solve the random string substitution problem from the question text:</p>

<pre><code>import  System.Random
import  Control.Monad.Random


-- generic monadic computation to get a sequence of "count" random items:
mkRandSeqM :: (RandomGen tg, Random tv) =&gt; (tv,tv) -&gt; Int -&gt; Rand tg [tv]
mkRandSeqM range count = sequence (replicate count (getRandomR range))

-- monadic computation to get our sort of random string:
mkRandStrM :: RandomGen tg =&gt; Rand tg String
mkRandStrM = mkRandSeqM  ('a', 'z')  10

-- monadic single string transformation:
randomizeM :: RandomGen tg =&gt; String -&gt; Rand tg String
randomizeM str =  if (str == "random")  then  mkRandStrM  else  (pure str)

-- monadic list-of-strings transformation:
mapRandomizeM :: RandomGen tg =&gt; [String] -&gt; Rand tg [String]
mapRandomizeM = mapM randomizeM

-- non-monadic function returning the altered string list and generator:
mapRandomize :: RandomGen tg =&gt; [String] -&gt; tg -&gt; ([String], tg)
mapRandomize lstr rng = runRand  (mapRandomizeM lstr)  rng


main = do
    let inpList  = ["random", "foo", "random", "bar", "random", "boo", "qux"]
    -- get a random number generator:
    let mySeed  = 54321
    let rng1    = mkStdGen mySeed  

    -- execute the string substitutions:
    let (outList, rng2) = mapRandomize inpList rng1

    -- display results:
    putStrLn $ "inpList = " ++ (show inpList)
    putStrLn $ "outList = " ++ (show outList)
</code></pre>

<p><br>
Note that above, RandomGen is the class of the generator, while Random is just the class of the generated value.</p>

<h2>Program output:</h2>

<pre><code>$ random1.x
inpList = ["random","foo","random","bar","random","boo","qux"]
outList = ["gahuwkxant","foo","swuxjgapni","bar","zdjqwgpgqa","boo","qux"]
$ 
</code></pre>
How can I generate different random values in Haskell?
<p>Suppose that I have a list like this:</p>

<pre class="lang-hs prettyprint-override"><code>let list = ["random", "foo", "random", "bar", "random", "boo"]
</code></pre>

<p>I want to iterate over a list and map all "random" elements to different  random strings:</p>

<pre class="lang-hs prettyprint-override"><code>let newList = fmap randomize list
print newList
-- ["dasidias", "foo", "gasekir", "bar", "nabblip", "boo"]
</code></pre>

<p>My randomize function looks like this:</p>

<pre class="lang-hs prettyprint-override"><code>randomize :: String -&gt; String
randomize str = 
  case str of
    "random" -&gt; randStr
    _        -&gt; str
  where
    randStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen
</code></pre>

<p>But I get the same random string for every "random" element:</p>

<pre class="lang-hs prettyprint-override"><code>["abshasb", "foo", "abshasb", "bar", "abshasb", "boo"]
</code></pre>

<p>I can't figure out why is this happening and how to get a different random value for each occurrence of "random".</p>