IMHO, the best idea is to keep the generator in a strict state record. Then you can use the ordinary do
-Syntax to work with the generator. Seeding is done only once - at the beginning of the main program (or at the beginning of each thread). You can avoid IO by using the split
operation, which yields two random generators from one. (Different, of course).
As state is still pure, threadsafety can be guaranteed. Additionally, you can always escape state by giving a random generator to the function. This is useful for instance in case of automatic unit tests.