Random numbers, IO monad, and pure code
I'd suggest to change the type signature of randomDots to

randomDots :: [Dot] -&gt; [Double] -&gt; R.RVar [Dot]


as there is nothing IO based there. And then change main to

main = mainWith . dotsToDiagram =&lt;&lt; R.sample (randomDots [] [0.01, 0.02..1.0])


Nitpicks: I'd put tooClose before randomDots so that the functions are in logical order, but that's just a matter of personal preferences.

Otherwise I also quite liked the code.

Update: Some further thoughts: Function randomDots is concise, but it also mixes several concepts together. Namely:


it re-runs generation of a dot (until it fits wrt the previous ones);
it checks each generated dot with all previously generated ones.


So I'd suggest to split the function into even smaller ones, each targeting one of these problems. While the resulting code is larger, I believe it's easier to comprehend and more maintainable in the long run:

-- | Runs a monadic action while its result satisfies a given predicate.
iterateWhile :: (Monad m) =&gt; (a -&gt; Bool) -&gt; m a -&gt; m a
iterateWhile pred k = loop
  where
    loop = k &gt;&gt;= \x -&gt; if pred x then loop else return x


This function is also available in monad-loops. Then:

-- | Generates a list of values that are independent wrt a given (commutative)
-- predicate.
-- Each monadic action is repeated until its result is independent with all the
-- previous ones.
genIndependent :: (Monad m) =&gt; (a -&gt; a -&gt; Bool) -&gt; [m a] -&gt; m [a]
genIndependent pred = foldM step []
  where
    step rs k = liftM (: rs) $ iterateWhile (\x -&gt; any (pred x) rs) k


Both these functions are polymorphic with no coupling with RVar. The final function is then expressed just as

-- |Recursively generate random dots and check that they do not
-- overlap.
randomDots :: [Double] -&gt; R.RVar [Dot]
randomDots = genIndependent tooClose . map (R.sample . randomDot)