2

My code aims to create a word search puzzle. There is a data called Orientation representing the direction of each word in the puzzle.

data Orientation =
  Forward | Back | Up | Down | UpForward | UpBack | DownForward | DownBack
  deriving (Eq, Ord, Show, Read)

Now given a input of strings which is [String], I want to randomly assign each string an orientation like [(Orientation, String)]

assignWordDir :: [String] -> [(Orientation, String)]
assignWordDir [] = []
assignWordDir (s:strs) = (ori, s) : assignWordDir
                        where ori = pickOri [Forward, Back, Up, Down, UpForward, UpBack, DownForward, DownBack]

pickOri :: [a] -> IO a
pickOri xs = do
  i <- randomRIO (0, len)
  pure $ xs !! i
  where len = length xs - 1

I cannot compile because the output of pickOri is IO Orientation, is there any suggestions on how to modify my code? Thanks a lot

Couldn't match expected type ‘[(IO Orientation, String)]’
                  with actual type ‘[String] -> [(Orientation, String)]’
sjakobi
  • 3,546
  • 1
  • 25
  • 43
Takumi
  • 23
  • 3
  • 1
    You can't, unless you change the type to `assignWordDir :: [String] -> IO [(Orientation, String)]`. Would that be acceptable? The main "rule" of IO is that to run IO in a function it has to have type `... -> IO ...`. – chi Dec 03 '20 at 20:00
  • I have tried ```IO [(Orientation, String)]``` and it couldn't compile, when I used ```[(IO orientation, String)]```, it can compile, but when giving it some strings, error occurs, "• No instance for (Show (IO Orientation)) arising from a use of ‘print’ • In a stmt of an interactive GHCi command: print it" – Takumi Dec 03 '20 at 20:16
  • You don't need to involve IO just because you need randomness. IO has a tendency to percolate thru your whole source code once invited in. See [similar question](https://stackoverflow.com/questions/60350962/another-question-about-random-numbers-in-haskell/60354483#60354483). Given a list of random integers, you can use `toEnum` to generate random orientations and `zip` to pair random orientations with your String's. – jpmarinier Dec 03 '20 at 20:16
  • Thank you. It works. – Takumi Dec 03 '20 at 20:35

1 Answers1

4

You might consider modifying the functions so that they stay pure by taking a RandomGen parameter. The pickOri function, for example, might be modified thusly:

pickOri :: RandomGen g => g -> [a] -> (a, g)
pickOri rnd xs =
  let len = length xs - 1
      (i, g) = randomR (0, len) rnd
  in (xs !! i, g)

It's necessary to return the new RandomGen value g together with the selected list element, so that it'll generate another pseudo-random number the next time around.

Likewise, you can modify assignWordDir like this:

assignWordDir :: RandomGen g => g -> [b] -> [(Orientation, b)]
assignWordDir _ [] = []
assignWordDir rnd (s:strs) = (ori, s) : assignWordDir g strs
  where (ori, g) =
    pickOri rnd [Forward, Back, Up, Down, UpForward, UpBack, DownForward, DownBack]

Notice that when recursing into to assignWordDir, the recursive function call uses the g it receives from pickOri.

You can use mkStdGen or newStdGen to produce RandomGen values. Here's an example using newStdGen:

*Q65132918> rnd <- newStdGen
*Q65132918> assignWordDir rnd ["foo", "bar", "baz"]
[(UpBack,"foo"),(Up,"bar"),(UpBack,"baz")]
*Q65132918> assignWordDir rnd ["foo", "bar", "baz"]
[(UpBack,"foo"),(Up,"bar"),(UpBack,"baz")]

Notice that when you use the same RandomGen value, you get the same sequence. That's because assignWordDir is a pure function, so that's expected.

You can, however, produce a new random sequence by creating or getting a new StdGen value:

*Q65132918> rnd <- newStdGen
*Q65132918> assignWordDir rnd ["foo", "bar", "baz"]
[(Up,"foo"),(Up,"bar"),(Forward,"baz")]

If you want to play with this in a compiled module, you can keep these functions as presented here, and then compose them with a newStdGen-generated StdGen in the main entry point.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736