1

I have random number generator

rand :: Int -> Int -> IO Int
rand low high = getStdRandom (randomR (low,high))

and a helper function to remove an element from a list

removeItem _ []                 = []
removeItem x (y:ys) | x == y    = removeItem x ys
                    | otherwise = y : removeItem x ys

I want to shuffle a given list by randomly picking an item from the list, removing it and adding it to the front of the list. I tried

shuffleList :: [a] -> IO [a]
shuffleList [] = []
shuffleList l = do 
                     y <- rand 0 (length l)
                     return( y:(shuffleList (removeItem  y l) ) )

But can't get it to work. I get

hw05.hs:25:33: error:
    * Couldn't match expected type `[Int]' with actual type `IO [Int]'
    * In the second argument of `(:)', namely
     ....

Any idea ?

Thanks!

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Tomer
  • 1,159
  • 7
  • 15
  • 1
    You need to `res <- ...` the recursive IO call before using the result `return (y:res)`. – chi Feb 26 '20 at 08:22

2 Answers2

2

Since shuffleList :: [a] -> IO [a], we have shuffleList (xs :: [a]) :: IO [a].

Obviously, we can't cons (:) :: a -> [a] -> [a] an a element onto an IO [a] value, but instead we want to cons it onto the list [a], the computation of which that IO [a] value describes:

   do
         y <- rand 0 (length l)
         -- return ( y : (shuffleList (removeItem  y l) ) )
         shuffled  <-     shuffleList (removeItem  y l)
         return      y : shuffled

In do notation, values to the right of <- have types M a, M b, etc., for some monad M (here, IO), and values to the left of <- have the corresponding types a, b, etc..

The x :: a in x <- mx gets bound to the pure value of type a produced / computed by the M-type computation which the value mx :: M a denotes, when that computation is actually performed, as a part of the combined computation represented by the whole do block, when that combined computation is performed as a whole.

And if e.g. the next line in that do block is y <- foo x, it means that a pure function foo :: a -> M b is applied to x and the result is calculated which is a value of type M b, denoting an M-type computation which then runs and produces / computes a pure value of type b to which the name y is then bound.

The essence of Monad is thus this slicing of the pure inside / between the (potentially) impure, it is these two timelines going on of the pure calculations and the potentially impure computations, with the pure world safely separated and isolated from the impurities of the real world. Or seen from the other side, the pure code being run by the real impure code interacting with the real world (in case M is IO). Which is what computer programs must do, after all.


Your removeItem is wrong. You should pick and remove items positionally, i.e. by index, not by value; and in any case not remove more than one item after having picked one item from the list.

The y in y <- rand 0 (length l) is indeed an index. Treat it as such. Rename it to i, too, as a simple mnemonic.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • I get error on the code above : Couldn't match type `a' with `Int' `a' is a rigid type variable bound by the type signature for: shuffleList :: forall a. [a] -> IO [a]... – Tomer Feb 26 '20 at 08:39
  • 1
    you need to change the signature to `shuffleList :: [Int] -> IO [Int]`. but that's the wrong fix. Your *`removeItem`* is the problem. You should pick and remove items positionally, not by value. – Will Ness Feb 26 '20 at 08:47
-1

Generally, with Haskell it works better to maximize the amount of functional code at the expense of non-functional (IO or randomness-related) code.

In your situation, your “maximum” functional component is not removeItem but rather a version of shuffleList that takes the input list and (as mentioned by Will Ness) a deterministic integer position. List function splitAt :: Int -> [a] -> ([a], [a]) can come handy here. Like this:

funcShuffleList :: Int -> [a] -> [a]
funcShuffleList _ [] = []
funcShuffleList pos ls =
    if (pos <=0) || (length(take (pos+1) ls) < (pos+1))
      then ls -- pos is zero or out of bounds, so leave list unchanged
      else let (left,right) = splitAt pos ls
           in  (head right) : (left ++ (tail right))

Testing:

 λ> 
 λ> funcShuffleList  4  [0,1,2,3,4,5,6,7,8,9]
 [4,0,1,2,3,5,6,7,8,9]
 λ> 
 λ> funcShuffleList 5 "@ABCDEFGH"
 "E@ABCDFGH"
 λ> 


Once you've got this, you can introduce randomness concerns in simpler fashion. And you do not need to involve IO explicitely, as any randomness-friendly monad will do:

shuffleList :: MonadRandom mr => [a] -> mr [a]
shuffleList [] = return []
shuffleList ls =
    do
       let maxPos = (length ls) - 1
       pos <- getRandomR (0, maxPos)
       return (funcShuffleList pos ls)

... IO being just one instance of MonadRandom.

You can run the code using the default IO-hosted random number generator:

main = do
    let inpList = [0,1,2,3,4,5,6,7,8]::[Integer]

    putStrLn $ "inpList  = " ++ (show inpList)

    -- mr automatically instantiated to IO:
    outList1 <- shuffleList inpList
    putStrLn $ "outList1 = " ++ (show outList1)
    outList2 <- shuffleList outList1
    putStrLn $ "outList2 = " ++ (show outList2)

Program output:

$ pickShuffle
inpList  = [0,1,2,3,4,5,6,7,8]
outList1 = [6,0,1,2,3,4,5,7,8]
outList2 = [8,6,0,1,2,3,4,5,7]
$ 
$ pickShuffle
inpList  = [0,1,2,3,4,5,6,7,8]
outList1 = [4,0,1,2,3,5,6,7,8]
outList2 = [2,4,0,1,3,5,6,7,8]
$ 

The output is not reproducible here, because the default generator is seeded by its launch time in nanoseconds.

If what you need is a full random permutation, you could have a look here and there - Knuth a.k.a. Fisher-Yates algorithm.

jpmarinier
  • 4,427
  • 1
  • 10
  • 23
  • 1. the function that takes the input list and an integer position *is* `removeItem`. 2. the repeated calls as you show them result in non-uniform shuffling unlike the OP's code with `removeItem`. 3. measuring `length` is redundant, can be done with integer comparison, `length ls` should be known before the function call. – Will Ness Feb 26 '20 at 14:58
  • 4. the name "shuffleList" is confusing when only one card is shuffled – Will Ness Feb 26 '20 at 15:03
  • Thanks for the hints. I do agree that “shuffleList” is highly misleading, but it is the OP's nomenclature. Maybe use `moveElemToHead` and `moveRandomElemToHeadM` with M standing for monadic. – jpmarinier Feb 26 '20 at 15:14
  • Regarding point 2, I am just putting a 2nd call to `shuffleList` in my example in order to show that calls can be sequenced easily; there is no higher claim. I think it is not clear from the text of the question whether or not the OP intends the `shuffleList` function to be a stepping stone towards a properly balanced whole list shuffling. I have just put a reference to Fisher-Yates at the end of my answer to cover for that risk. – jpmarinier Feb 26 '20 at 15:46
  • I think their algo is already conforming to the first version in the haskellwiki link. – Will Ness Feb 26 '20 at 15:51