1

Having absolutely zero experience with Haskell, I need to come up with a code equivalent to this Python one:

from random import choice, sample

def random_subset():
    return tuple(sample(('N', 'S', 'W', 'E'), choice((1, 2, 3, 4))))

def form_grid(n):
    return [[random_subset() for _ in range(n)] for _ in range(n)]

form_grid(10)

which produces something like this:

N     ESWN  SNEW  NSE   EWSN  E     ENSW  N     NSWE  WES   
NE    WNS   SWEN  EWN   ENWS  WEN   WS    W     ENSW  NW    
WENS  NWE   SNEW  ES    E     S     ES    SENW  EW    WEN   
NSE   NE    WNE   NEWS  SNE   W     SWNE  NSWE  SNEW  EN    
S     SNW   WNES  S     WESN  E     ES    N     ESN   ES    
SWEN  S     WSNE  NEWS  WESN  E     S     SE    E     N     
NEW   S     NEW   WS    W     EN    N     NWS   E     WENS  
WN    NWE   S     SEW   NESW  EWSN  WENS  ES    NWS   WN    
W     NWE   N     N     ES    E     E     WN    SWNE  NES   
WENS  NWE   NW    WESN  SW    NES   ENWS  SE    N     SWNE 

I, for the love of god, can't wrap my head around Haskell's concept of IO (randomness in particular). The best I could come up with is this:

import Data.Random hiding (shuffle, sample)
import Data.Random.Source.Std
import Data.Random.Extras

randSubset :: IO [Char]
randSubset = do
    len <- runRVar (choice [1..4]) StdRandom :: IO Int
    subset <- runRVar (sample len ['N', 'S', 'W', 'E']) StdRandom :: IO [Char]
    return subset

formGrid :: Int -> [[IO [Char]]]
formGrid n = [[subset | _ <- [0..(n - 1)], subset <- randSubset] | _ <- [0..(n - 1)]]

which still didn't do it:

error:
    * Couldn't match expected type `[IO [Char]]'
                  with actual type `IO [Char]'
    * In the expression: randSubset
      In a stmt of a list comprehension: subset <- randSubset
      In the expression:
        [subset | _ <- [0 .. (n - 1)], subset <- randSubset]
   |
12 | formGrid n = [[subset | _ <- [0..(n - 1)], subset <- randSubset] | _ <- [0..(n - 1)]]
   |                                                      ^^^^^^^^^^

Quick googling didn't help much - I probably didn't use the most accurate keywords for this problem I'm facing. Making random changes and hoping for the best is getting pretty frustrating, but I really have neither time, nor energy to dive into Haskell properly (even though it's a shame), so for now, I'd love someone to just point me to what's wrong with this code.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Enirsa
  • 85
  • 1
  • 7
  • 1
    `randSubset` creates an `IO [Char]`, so you can not iterate over that. – Willem Van Onsem May 22 '19 at 11:03
  • 3
    Have you learnt how to use monads and `do`-notation? You'll find all IO fairly difficult until you grok that. I'd recommend [the LYAH chapter on monads](http://learnyouahaskell.com/a-fistful-of-monads) if not the entirety of LYAH to understand this. – AJF May 22 '19 at 11:08
  • @Willem Van Onsem, you're right! In fact, I don't even want to iterate over that. I just want to take the whole subsets (new every time) and place them into the cells n*n times. How do I do that? – Enirsa May 22 '19 at 11:08

3 Answers3

5

Like the error already says, your list comprehension has a generator:

formGrid n = [[subset | _ <- [0..(n - 1)], subset <- randSubset] | _ <- [0..(n - 1)]]

So that means that it expects randSubset to be a list of something, but it is not a list of something, it is an IO of a list of something. You thus can not use that.

The type of your function is also a bit problematic, you use [[IO [Char]]], so a matrix of IO [Char]s.

You are likely looking for replicateM :: Monad m => Int -> m a -> m [a], so then your program looks like:

import Control.Monad(replicateM)

formGrid :: Int -> IO [[[Char]]]
formGrid n = replicateM n (replicateM n randSubset)

For example:

Main> formGrid 3
[["WSNE","WNS","S"],["WN","SN","WEN"],["SEWN","ESN","NESW"]]
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
2

To complement Willem's answer, I'll add that your randSubset looks rather complex. Here's a simpler alternative

randSubset :: IO String
randSubset = do
  n <- sample (Uniform 1 4)        -- choose how many elements
  sample (shuffleNofM n 4 "NSWE")  -- take that many elements

(That's sample from Data.Random, by the way)

You should check that this is indeed the intended distribution of subsets. Note that this is not a uniform distribution: N is more likely than NS (or even both NS and SN combined). Also note that each permutation of the same subset can occur, so we are not really sampling "subsets". I don't know which distribution is used by your Python code -- it might be the same, after all.

If you work inside IO, I think it's simpler if you use sample (someDistribution) rather than working at the lower level of RVars.

After this, you can use replicateM to generate your grid, as Willem showed.

chi
  • 111,837
  • 3
  • 133
  • 218
1

You tried to jump too far at once. Start small,

formRow :: Int -> IO [[Char]]
formRow 0 = return [] 
formRow n = do { 
     subset  <- randSubset ;     -- subset :: [Char]   <-  randSubset :: IO [Char]
     subsets <- formRow (n-1) ;  --           ~~~~~~                        ~~~~~~
     return (subset : subsets)   -- IO [[Char]]
     }

... just using what you already have, here. Then do the same with the rows,

formGrid :: Int -> IO [[[Char]]]
formGrid 0 = return [] 
formGrid n = do { 
     row  <- formRow n ;        -- row :: [[Char]]   <-  formRow n :: IO [[Char]]
     rows <- formGrid (n-1) ;   --        ~~~~~~~~                       ~~~~~~~~
     return (row : rows)        -- IO [[[Char]]]
     }

and you're done. Don't fear the do notation, it's your friend. It is easy to program in, as it is its whole purpose.

When you've coded your next monadic combinator, check out its type on Hoogle and see whether it already is in the library.

Indeed, abstracting, we see we've reimplemented

replicateM :: Monad m => Int -> m a -> m [a]

formRow n = replicateM n randSubset 
formGrid n = replicateM n (formRow n)
           = replicateM n (replicateM n randSubset)

(just as Willem's answer says; but now we know how and why).

Will Ness
  • 70,110
  • 9
  • 98
  • 181