To get used to Haskell, I wrote a little program using the Diagrams package which generates 100 random dots (larger dots are more likely to be in the center).
Sample output:

My questions:
Is the program well structured with respect to the IO monad and the random values (see types for
randomDotandrandomDots)? Should I organize the code differently to keep more code out of the IO monad?Other nitpicks?
module Main (main) where
import Data.Colour.SRGB
import Data.Random.Source
import Diagrams.Backend.SVG.CmdLine
import Diagrams.Prelude
import qualified Data.Random as R
data Dot = Dot { _dotCenter :: R2
, _radius :: Double
, _color :: Colour Double
} deriving Show
colors :: [Colour Double]
colors = map sRGB24read [
"bf3131",
"f5b456",
"a89178",
"615b5b",
"add274",
"b9a1b9",
"f0a2bc",
"eb565c",
"d15f69",
"48bdbe",
"f1ede2"]
-- |Generate a single dot with random location, radius, and color.
randomDot :: Double -> R.RVar Dot
randomDot x = do
let mu_rad = 15 * exp (-4 * x)
sigmaSq_rad = 0.3 * mu_rad
sigmaSq_loc = 8 * exp (2.5*x)
locX <- R.sample (R.normal 0 sigmaSq_loc)
locY <- R.sample (R.normal 0 sigmaSq_loc)
radius <- abs <$> R.sample (R.normal mu_rad sigmaSq_rad)
color <- R.sample (R.randomElement colors)
return $ Dot (r2 (locX, locY)) radius color
-- |Recursively generate random dots and check that they do not
-- overlap.
randomDots :: [Dot] -> [Double] -> IO [Dot]
randomDots dots [] = return dots
randomDots dots (x:xs) = do
dot <- R.sample $ randomDot x
if any (tooClose dot) dots
then randomDots dots (x:xs)
else randomDots (dot:dots) xs
tooClose :: Dot -> Dot -> Bool
tooClose x y = dist < 1.1 * radiusSum
where
dist = magnitude $ _dotCenter x ^-^ _dotCenter y
radiusSum = _radius x + _radius y
dotsToDiagram :: [Dot] -> Diagram B R2
dotsToDiagram = mconcat . map fromDot
fromDot :: Dot -> Diagram B R2
fromDot c = circle (_radius c) # fc (_color c)
# lw none
# translate (_dotCenter c)
main :: IO ()
main = mainWith . dotsToDiagram =<< randomDots [] [0.01, 0.02..1.0]