Simulating the martingale betting system with roulette I wrote a program that simulates many instances of trying the martingale betting system with roulette in Haskell.

At the answerer's suggestion I opted to leave the IO monad in main and ensure that the functions martingale and martingale', which handled the actual testing, were entirely pure, but the answerer also suggested that I could push my call to newStdGen all the way down into martingale'. I believe this would remove the requirement that my functions even require a StdGen as an argument.

My question then: Is it more natural for functions that compute random variable x to require StdGens as an argument, or for them to create StdGens and perhaps return IO x? Also, is my source code posted below reasonable and could it be improved or made more natural?

-- file: Martingale.hs
-- a program to simulate the martingale doubling system

import System.Random (randomR, newStdGen, StdGen)
import System.Environment (getArgs)
import Control.Monad (replicateM)

red = [1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36]

martingale :: StdGen -&gt; Bool
martingale = martingale' 1 0

martingale' :: Real a =&gt; a -&gt; a -&gt; StdGen -&gt; Bool
martingale' bet acc gen
  | acc &gt;= 5     = True
  | acc &lt;= -100  = False
  | otherwise    = 
    let (randNumber, newGen) = randomR (0,37) gen :: (Int, StdGen)
    in if randNumber `elem` red
       then martingale' 1 (acc + bet) newGen
       else martingale' (bet * 2) (acc - bet) newGen

main :: IO ()
main = do
  args &lt;- getArgs
  let iters = read $ head args
  gens &lt;- replicateM iters newStdGen
  let results = map martingale gens
      countWins = length $ filter (== True) results
      prob = fromIntegral countWins / fromIntegral iters
  print prob
Is it more natural for functions that compute random variable x to require StdGens as an argument, or for them to create StdGens and perhaps return IO x?


I think in most cases you're going to be better off passing around a pure source of randomness or a seed value so that you can guarantee deterministic output if need be by using the same seed in multiple runs. That is, you made the right choice.


  Also, is my source code posted below reasonable and could it be improved or made more natural?


I don't think there's anything wrong per se, but it's very narrowly focused without much opportunity for reusability. You'll not often go wrong if you attempt to mirror the reality of what you're modeling in your functions and type signatures. For instance, consider that a roulette wheel is an unlimited source of randomly chosen values. One way to express this in your Haskell program would be...

wheel :: StdGen -&gt; [Int]
wheel = randomRs (0, 37)


It's useful to encapsulate this because there's nothing about martingale betting systems that involves a random element, the system itself is entirely deterministic.

martingale :: Int        -- ^ Current bet
           -&gt; Bool       -- ^ Result of current spin
           -&gt; (Int, Int) -- ^ (Next bet, winnings/losings)
martingale bet True  = (1      ,        bet)
martingale bet False = (bet * 2, negate bet)


Here I've separated out just the betting system aspect of your program, because tracking winnings or choosing ranges are separate functions from determining bets. This way also you can substitute different betting strategies fairly easy as long as it has the same type signature.

You'd tie this all together with a simulation function something like this.

simulate :: (Int -&gt; Bool)               -- ^ A picking function
         -&gt; (Int -&gt; Bool -&gt; (Int, Int)) -- ^ A betting function
         -&gt; Int                         -- ^ An initial bet
         -&gt; (Int -&gt; Maybe Bool)         -- ^ Decides when to leave the table
         -&gt; [Int]                       -- ^ A wheel
         -&gt; Bool                        -- ^ Took winnings or unacceptable loss?


This is probably way overkill to start with, but with it you could test biased wheels, or different levels of acceptable losses, or different strategies against particular wheels. At this point also you'd want to start using type aliases, newtypes, or your own data types to disambiguate what's going on. E.g., you'd end up with...

simulate :: (Slot -&gt; Bool)
         -&gt; (Stake -&gt; Bool -&gt; (Stake, Winnings))
         -&gt; Stake
         -&gt; (Winnings -&gt; PlayState)
         -&gt; [Slot]
         -&gt; PlayState


And used like,

simulate betOnRed martingale 1 quitWhileAhead unbiasedWheel
simulate betOnLow antimartingale 50 bailBelowFifty houseBiasedWheel