So the problem is that I wanted to randomly generate these user profiles, so I ended up making this data type an instance of Random which is entirely uninteresting & boilerplate code.
Now, essentially the logic for generating the list of profiles looked like, exaggerated for full effect,
filepath = "crud.csv"
generateProfiles :: Int -> IO [Int]
generateProfiles i = foldM aux  [1..i]
where aux is i = do
sub <- randomIO
main = generateProfiles 10000000 >>= (writeFile filepath . unlines . map show)
Run this! Run this and weep for your feeble RAM as it is withers under the gaze of my space leak!
Alright, so this is pretty awful isn't it? It came from trying to think about the problem as "well, I want to generate 'i' many subscribers so I'm going to iterate over a list of i length, generating a subscriber each time". Bzzt, wrong. That's trying to (badly) emulate a C style for-loop in Haskell, not writing Haskell, and is a very bad idea.
Well, I took a look at this code with a fresh eye, was terrified, and then realized what would probably be a more idiomatic Haskell program: generate an infinite list of subscribers, and just write the first 'i' to a file. After all, if I'm truly being lazy then only as many as I need to write should be created. So the new approach looks something like
betterGenerate :: (RandomGen g) => g -> [Int]
betterGenerate = randoms
main = do
std <- newStdGen
let subs = betterGenerate std
(writeFile filepath . unlines . map show . take 10000000) subs
which takes a few seconds, but doesn't eat RAM all to Hell. The main point here, for me, was to stop trying to write C# in Haskell and just write Haskell. There's probably still ways I could improve the logic of my little utility, but I can now generate 1 million subscribers, or about 120 MB of data, in roughly 2 minutes which is the upper limit of what I need.