I’m pretty new to Haskell although I did some ML many moons ago.
I’m trying to set up a deck of playing cards, shuffle them and then deal them. I have the deck and shuffle sorted (of a fashion) but I’m not sure what I’m doing after that.
This is what I have so far:
import System.Random
import Data.Array.IO
import Control.Monad
-- | Randomly shuffle a list
-- /O(N)/
shuffle :: [a] -> IO [a]
shuffle xs = do
ar <- newArray n xs
forM [1..n] $ i -> do
j <- randomRIO (i,n)
vi <- readArray ar i
vj <- readArray ar j
writeArray ar j vi
return vj
where
n = length xs
newArray :: Int -> [a] -> IO (IOArray Int a)
newArray n xs = newListArray (1,n) xs
ranks=[2..14]
suits=[1,2,3,4]
cards=[(r,s) | r <- ranks, s<- suits]
deck=shuffle cards
myAction :: IO [(Integer, Integer)]
myAction = shuffle cards
main :: IO ()
main = do
xs <- myAction
print xs
There was no particular reason I chose that list shuffler other than the reason I could interrogate (or at least display) the resulting list.
I’d like to be able to pull items off the returned IO [(Integer, Integer)] but I’m not entirely sure how to proceed. I understand that I can’t simply convert it to a vanilla list (this is covered sufficiently elsewhere) so presumably I either need to:
- extend the IO (monad?) somehow
- write a custom list of tuples shuffler
- write my own monad
- use some other method I haven’t learnt yet
Anecdotally, I believe this can be done “uncleanly” but I don’t want to go to programmer hell until I understand how hot the fires are…
Any idea how best to proceed?
2
In abstract terms, what you should do is push the IO
part as “high” up in your code as possible, so that as little code as possible knows about IO.
In more concrete terms, for your problem, shuffle
and myAction
don’t need to know about IO, and also don’t need to know how the randomness is generated. They just need the randomness passed in as a parameter. So what you can do is, generate the random value in main
, and then pass it in as a pure value to myAction
and/or shuffle
.
You should end up with types like shuffle :: randomness -> [a] -> [a]
and myAction :: randomness -> [a]
, where “randomness” is generated in main
. Now your return values aren’t polluted with IO
, and it’s easy to work with them.*
AFAIK, this pattern — where you split your code into “pure” and “impure” portions (and hope that most of it is “pure” 🙂 ) — is pretty common in Haskell.
*: if you’re ever stuck in IO
, read up on functors (fmap
), applicatives (liftA2
, <*>
, etc.), monads (liftM
, >>=
, etc.), and lifting. It’s still a pain, but using those you can work on the a
part of IO a
.
5