1

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
  • BTW, do {xs <- myAction; print xs} is better written as myAction >>= print.
    – user39685
    Commented Apr 9, 2014 at 12:24
  • OK, thanks. I wasn't really sure of what I was doing beyond deck=shuffle cards
    – Robbie Dee
    Commented Apr 9, 2014 at 12:47

1 Answer 1

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
  • OK, maybe if I just write something that takes a list and swaps two values I can call this repeatedly rather than use IO. Or at least until I've learnt a little more Haskell.
    – Robbie Dee
    Commented Apr 9, 2014 at 12:52
  • 1
    @RobbieDee yeah, make sure that your shuffle function is pure. Then it's easy to test and to play with in ghci. You can always use pure stuff from evil code, but you (generally) can't do something evil without becoming evil yourself! (evil = using IO, pure = not using IO)
    – user39685
    Commented Apr 9, 2014 at 13:04
  • Wow, this whole pure vs impure thing is kicking my butt. You can't seem to do anything pure with random since it isn't (or shouldn't be) repeatable. I can't even seem to pass the random data into the program as that then becomes impure. For now I've got another imperative program generating random numbers list which I then drop into the source code. Not perfect, but it'll have to do me for now.
    – Robbie Dee
    Commented Apr 10, 2014 at 12:06
  • @RobbieDee the place where you generate the random value is impure (since presumably you want it to be actually random, which means it'll depend on some outside state), but where you use it is pure. So in your main function, generate a random value. But then pass it as a simple parameter wherever you want to use it.
    – user39685
    Commented Apr 10, 2014 at 13:02
  • Ah, OK. I was thinking anything in main had to go through the IO monad but you're right, I can just do: main = print $ cardTrick [My 52 random ints]. Thanks man! :)
    – Robbie Dee
    Commented Apr 15, 2014 at 12:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.