4

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.

Sergii Sopin
  • 411
  • 2
  • 11
  • 7
    Short answer: you don't. You just build an even bigger `IO` action that computes the result. You never actually "get out" a `UUID` from a `IO UUID`. See this question: [How to get normal value from IO action in Haskell](https://stackoverflow.com/questions/11467066/how-to-get-normal-value-from-io-action-in-haskell) – HTNW Nov 18 '19 at 00:50
  • Do you understand (1) what referential transparency means, (2) that Haskell is a referentially transparent language, and (3) that what you're asking for would violate referential transparency? – Joseph Sible-Reinstate Monica Nov 18 '19 at 02:47
  • Yes, I understand. By the way, I also asked if there are other ways to do what I need to do, which is create a data type that contains a unique identifier and create an instance of Eq for this data type based on this identifier. – Sergii Sopin Nov 18 '19 at 02:56
  • 2
    By the way, for the `instance Eq` part, if you define only `==`, a default definition of `/=` will be used that matches what you've written, so you can leave it out. – K. A. Buhr Nov 18 '19 at 04:57
  • This may also be of interest: https://stackoverflow.com/questions/6311512/creating-unique-labels-in-haskell/63406066 . – atravers Sep 21 '20 at 03:21

1 Answers1

6

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.

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71