【问题标题】:Haskell UUID generationHaskell UUID 生成
【发布时间】:2019-11-21 04:55:28
【问题描述】:

我是 Haskell 的新手,需要帮助。我正在尝试构建一个必须以某种方式唯一的新数据类型,因此我决定使用 UUID 作为唯一标识符:

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

这样,我可以做到以下几点:

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

问题是(对我而言)所有已知的 UUID 生成器都会生成 IO UUID,但我需要在上面提到的纯代码中使用它。您能否建议是否有任何方法可以从 IO UUID 中提取 UUID,或者是否有更好的方法可以在 Haskell 中执行我需要的操作?谢谢。


更新

感谢所有伟大的建议和代码示例。从这里发布的内容来看,我可以说你不能破坏referential transparency,但是有一些聪明的方法可以在不破坏它的情况下解决问题,并且可能是最优化的方法,列在下面的答案中。

还有一种替代方法,我可以根据提供的使用 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'

不确定它是否是最优雅的实现,但它确实有效。

【问题讨论】:

  • 简短回答:你不知道。您只需构建一个更大的IO 操作来计算结果。你从来没有真正从IO UUID 中“退出”UUID。看到这个问题:How to get normal value from IO action in Haskell
  • 你明白 (1) 什么是引用透明,(2) Haskell 是一种引用透明的语言,(3) 你所要求的会违反引用透明吗?
  • 是的,我明白了。顺便问一下,是否还有其他方法可以做我需要做的事情,即创建一个包含唯一标识符的数据类型,并根据该标识符为该数据类型创建一个 Eq 实例。
  • 顺便说一下,对于instance Eq部分,如果您只定义==,将使用与您所写内容匹配的/=的默认定义,因此您可以离开它出来。
  • 这可能也很有趣:stackoverflow.com/questions/6311512/….

标签: haskell


【解决方案1】:

如果您正在查看 uuid 包中的函数,那么 UUID 有一个 Random 实例。这意味着可以使用来自System.Random 的标准函数使用种子在纯代码中生成一系列随机 UUID:

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]

请注意,someUUIDs 每次调用时都会创建相同的三个“唯一”UUID,因为种子是硬编码的。

与所有纯 Haskell 代码一样,除非您作弊(使用不安全的函数),否则您不能期望在调用之间不显式传递某些状态(在本例中为 StdGenRNG)的情况下生成一系列实际唯一的 UUID到random

避免传递生成器的丑陋样板的通常解决方案是在可以保持所需状态的 monad 中运行至少部分代码。有些人喜欢使用MonadRandom 包,尽管您也可以在该州的某个地方使用带有StdGen 的常规State monad。 MonadRandom 相对于 State 的主要优点是您可以获得一些专用语法 (getRandom),并且可以创建一个包含 RandomTStateT 的 monad 堆栈,这样您就可以将您的 RNG 状态与其他状态区分开来您的应用程序状态。

使用MonadRandom,您可以编写如下应用程序:

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

请注意,应用程序的大部分内容需要用 monadic 语法编写并在应用程序 M monad 中运行。这不是一个很大的限制——大多数重要的应用程序都将用 some monad 编写。

【讨论】:

    猜你喜欢
    • 2016-10-23
    • 2017-06-25
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 2016-02-25
    • 1970-01-01
    • 1970-01-01
    • 2021-08-29
    相关资源
    最近更新 更多