01AutoMonkey
01AutoMonkey

Reputation: 2809

How do I recursively use newStdGen in Haskell? (to get different random results on each iteration)

I use System.Random and System.Random.Shuffle to shuffle the order of characters in a string, I shuffle it using:

shuffle' string (length string) g

g being a getStdGen.

Now the problem is that the shuffle can result in an order that's identical to the original order, resulting in a string that isn't really shuffled, so when this happens I want to just shuffle it recursively until it hits a a shuffled string that's not the original string (which should usually happen on the first or second try), but this means I need to create a new random number generator on each recursion so it wont just shuffle it exactly the same way every time.

But how do I do that? Defining a

newg = newStdGen

in "where", and using it results in:

Jumble.hs:20:14:
    Could not deduce (RandomGen (IO StdGen))
      arising from a use of shuffle'
    from the context (Eq a)
      bound by the inferred type of
               shuffleString :: Eq a => IO StdGen -> [a] -> [a]
      at Jumble.hs:(15,1)-(22,18)
    Possible fix:
      add an instance declaration for (RandomGen (IO StdGen))
    In the expression: shuffle' string (length string) g
    In an equation for `shuffled':
        shuffled = shuffle' string (length string) g
    In an equation for `shuffleString':
        shuffleString g string
          = if shuffled == original then
                shuffleString newg shuffled
            else
                shuffled
          where
              shuffled = shuffle' string (length string) g
              original = string
              newg = newStdGen

Jumble.hs:38:30:
    Couldn't match expected type `IO StdGen' with actual type `StdGen'
    In the first argument of `jumble', namely `g'
    In the first argument of `map', namely `(jumble g)'
    In the expression: (map (jumble g) word_list)

I'm very new to Haskell and functional programming in general and have only learned the basics, one thing that might be relevant which I don't know yet is the difference between "x = value", "x <- value", and "let x = value".

Complete code:

import System.Random
import System.Random.Shuffle

middle :: [Char] -> [Char]
middle word
  | length word >= 4 = (init (tail word))
  | otherwise = word

shuffleString g string =
  if shuffled == original
    then shuffleString g shuffled
    else shuffled
  where
  shuffled = shuffle' string (length string) g
  original = string

jumble g word
  | length word >= 4 = h ++ m ++ l
  | otherwise = word
  where
  h = [(head word)]
  m = (shuffleString g (middle word))
  l = [(last word)]

main = do
  g <- getStdGen
  putStrLn "Hello, what would you like to jumble?"
  text <- getLine
  -- let text = "Example text"
  let word_list = words text
  let jumbled = (map (jumble g) word_list)
  let output = unwords jumbled
  putStrLn output

Upvotes: 0

Views: 410

Answers (1)

bheklilr
bheklilr

Reputation: 54078

This is pretty simple, you know that g has type StdGen, which is an instance of the RandomGen typeclass. The RandomGen typeclass has the functions next :: g -> (Int, g), genRange :: g -> (Int, Int), and split :: g -> (g, g). Two of these functions return a new random generator, namely next and split. For your purposes, you can use either quite easily to get a new generator, but I would just recommend using next for simplicity. You could rewrite your shuffleString function to something like

shuffleString :: RandomGen g => g -> String -> String
shuffleString g string =
    if shuffled == original
        then shuffleString (snd $ next g) shuffled
        else shuffled
    where
        shuffled = shuffle' string (length string) g
        original = string

End of answer to this question


One thing that might be relevant which I don't know yet is the difference between "x = value", "x <- value", and "let x = value".

These three different forms of assignment are used in different contexts. At the top level of your code, you can define functions and values using the simple x = value syntax. These statements are not being "executed" inside any context other than the current module, and most people would find it pedantic to have to write

module Main where

let main :: IO ()
    main = do
        putStrLn "Hello, World"
        putStrLn "Exiting now"

since there isn't any ambiguity at this level. It also helps to delimit this context since it is only at the top level that you can declare data types, type aliases, and type classes, these can not be declared inside functions.

The second form, let x = value, actually comes in two variants, the let x = value in <expr> inside pure functions, and simply let x = value inside monadic functions (do notation). For example:

myFunc :: Int -> Int
myFunc x =
    let y = x + 2
        z = y * y
    in z * z

Lets you store intermediate results, so you get a faster execution than

myFuncBad :: Int -> Int
myFuncBad x = (x + 2) * (x + 2) * (x + 2) * (x + 2)

But the former is also equivalent to

myFunc :: Int -> Int
myFunc x = z * z
    where
        y = x + 2
        z = y * y

There are subtle difference between let ... in ... and where ..., but you don't need to worry about it at this point, other than the following is only possible using let ... in ..., not where ...:

myFunc x = (\y -> let z = y * y in z * z) (x + 2)

The let ... syntax (without the in ...) is used only in monadic do notation to perform much the same purpose, but usually using values bound inside it:

something :: IO Int
something = do
    putStr "Enter an int: "
    x <- getLine
    let y = myFunc (read x)
    return (y * y)

This simply allows y to be available to all proceeding statements in the function, and the in ... part is not needed because it's not ambiguous at this point.

The final form of x <- value is used especially in monadic do notation, and is specifically for extracting a value out of its monadic context. That may sound complicated, so here's a simple example. Take the function getLine. It has the type IO String, meaning it performs an IO action that returns a String. The types IO String and String are not the same, you can't call length getLine, because length doesn't work for IO String, but it does for String. However, we frequently want that String value inside the IO context, without having to worry about it being wrapped in the IO monad. This is what the <- is for. In this function

main = do
    line <- getLine
    print (length line)

getLine still has the type IO String, but line now has the type String, and can be fed into functions that expect a String. Whenever you see x <- something, the something is a monadic context, and x is the value being extracted from that context.

So why does Haskell have so many different ways of defining values? It all comes down to its type system, which tries really hard to ensure that you can't accidentally launch the missiles, or corrupt a file system, or do something you didn't really intend to do. It also helps to visually separate what is an action, and what is a computation in source code, so that at a glance you can tell if an action is being performed or not. It does take a while to get used to, and there are probably valid arguments that it could be simplified, but changing anything would also break backwards compatibility.

And that concludes today's episode of Way Too Much Information(tm)

(Note: To other readers, if I've said something incorrect or potentially misleading, please feel free to edit or leave a comment pointing out the mistake. I don't pretend to be perfect in my descriptions of Haskell syntax.)

Upvotes: 2

Related Questions