Sergii S.
Sergii S.

Reputation: 449

Haskell UUID generation

I am new to Haskell and need help. I am trying to build a new data type that has to be somehow unique, so I decided to use UUID as a unique identifier:

data MyType = MyType {
   uuid :: UUID,
   elements :: AnotherType
}

in this way, I can do following:

instance Eq MyType where
    x == y = uuid x == uuid y
    x /= y = not (x == y)

The problem is that all known (to me) UUID generators produce IO UUID, but I need to use it in a pure code as mentioned above. Could you please suggest if there is any way to extract UUID out of IO UUID, or maybe be there is a better way to do what I need in Haskell? Thanks.


UPDATE

Thanks for all the great suggestions and the code example. From what is posted here I can say you cannot break a referential transparency, but there are smart ways how to solve the problem without breaking it and, probably the most optimal one, is listed in the answer below.

There is also one alternative approach that I was able to explore myself based on provided recommendations with the usage of State Monad:

type M = State StdGen
type AnotherType = String

data MyType = MyType {
    uuid :: UUID,
    elements :: AnotherType
} deriving (Show)

mytype :: AnotherType -> M MyType
mytype x = do
    gen <- get
    let (val, gen') = random gen
    put gen'
    return $ MyType val x

main :: IO ()
main = do
    state <- getStdGen
    let (result, newState) = runState (mytype "Foo") state
    putStrLn $ show result
    let (result', newState') = runState (mytype "Bar") newState
    setStdGen newState'
    putStrLn $ show result'

Not sure if it is the most elegant implementation, but it works.

Upvotes: 4

Views: 1692

Answers (1)

K. A. Buhr
K. A. Buhr

Reputation: 51039

If you're looking at the functions in the uuid package, then UUID has a Random instance. This means that it's possible to generate a sequence of random UUIDs in pure code using standard functions from System.Random using a seed:

import System.Random
import Data.UUID

someUUIDs :: [UUID]
someUUIDs =
  let seed = 123
      g0 = mkStdGen seed -- RNG from seed
      (u1, g1) = random g0
      (u2, g2) = random g1
      (u3, g3) = random g2
  in [u1,u2,u3]

Note that someUUIDs creates the same three "unique" UUIDs every time it's called because the seed is hard-coded.

As with all pure Haskell code, unless you cheat (using unsafe functions), you can't expect to generate a sequence of actually unique UUIDs without explicitly passing some state (in this case, a StdGen RNG) between calls to random.

The usual solution to avoid the ugly boilerplate of passing the generator around is to run at least part of your code within a monad that can maintain the needed state. Some people like to use the MonadRandom package, though you can also use the regular State monad with a StdGen somewhere in the state. The main advantages of MonadRandom over State is that you get some dedicated syntax (getRandom) and can create a monad stack that includes both RandomT and StateT so you can separate your RNG state from the rest of your application state.

Using MonadRandom, you might write an application like:

import Control.Monad.Random.Strict
import System.Random
import Data.UUID

-- monad for the application
type M = Rand StdGen

-- get a generator and run the application in "M"
main :: IO ()
main = do
  g <- getStdGen  -- get a timestamp-seeded generator
  let log = evalRand app g  -- run the (pure) application in the monad
  putStr log

-- the "pure" application, running in monad "M"
app :: M String
app = do
  foo <- myType "foo"
  bar <- myType "bar"
  -- do some processing
  return $ unlines ["Results:", show foo, show bar]

type AnotherType = String
data MyType = MyType {
   uuid :: UUID,
   elements :: AnotherType
} deriving (Show)

-- smart constructor for MyType with unique UUID
myType :: AnotherType -> M MyType
myType x = MyType <$> getRandom <*> pure x

Note that substantial parts of the application will need to be written in monadic syntax and run in the application M monad. This isn't a big restriction -- most non-trivial applications are going to be written in some monad.

Upvotes: 7

Related Questions