最好的方法是什么?unsafePerformIO
? 模板 Haskell?还有什么?我从来没有使用过其中任何一个,所以我不知道使用它们的许多细节。
请注意,程序每次运行时都会被编译,所以我在编译时或运行时生成字符串都没有关系。我还需要在整个代码的很多地方使用这个字符串,所以我不能真正以“正确”的方式来做它,让它成为一个 IO 操作,这需要太多其他代码放入 IO monad .
最好的方法是什么?unsafePerformIO
? 模板 Haskell?还有什么?我从来没有使用过其中任何一个,所以我不知道使用它们的许多细节。
请注意,程序每次运行时都会被编译,所以我在编译时或运行时生成字符串都没有关系。我还需要在整个代码的很多地方使用这个字符串,所以我不能真正以“正确”的方式来做它,让它成为一个 IO 操作,这需要太多其他代码放入 IO monad .
我不建议使用unsafePerformIO
. 我猜 Haskell 报告并没有说明一个常量函数是被记忆的,所以它可能会发生
randStringUnsafe :: String
randStringUnsafe = unsafePerformIO $ liftM (take 10 . randomRs ('a','z')) newStdGen
不同的通话会给你不同的结果!使用 GHC,它很可能会被记住,但没有保证。例如,如果编译器内联函数怎么办?(GHC 可能足够聪明,不会这样做,但同样不能保证......)。例如
randNumUnsafe :: (Random a, Num a) => [a]
randNumUnsafe = unsafePerformIO $ liftM (take 10 . randomRs (0, 9)) newStdGen
每次调用它肯定会给你不同的结果。
我宁愿使用 Template Haskell。它可能有点复杂,但安全。在一个模块中,我们定义
{-# LANGUAGE TemplateHaskell #-}
module RandomTH where
import Control.Monad
import System.Random
import Language.Haskell.TH
-- A standard function generating random strings.
randString :: IO String
randString = liftM (take 10 . randomRs ('a','z')) newStdGen
-- .. lifted to Q
randStringQ :: Q String
randStringQ = runIO randString
-- .. lifted to an Q Exp
randStringExp :: Q Exp
randStringExp = randStringQ >>= litE . stringL
-- | Declares a constant `String` function with a given name
-- that returns a random string generated on compile time.
randStringD :: String -> DecsQ
randStringD fname = liftM (: []) $
funD (mkName fname) [clause [] (normalB randStringExp) []]
(也许randStringD
可以以更易读的方式编写 - 如果您有想法,请编辑或评论。)
然后,在另一个模块中,我们可以使用它来声明一个具有给定名称的常量函数:
{-# LANGUAGE TemplateHaskell #-}
$(randStringD "randStr")
main = do
putStrLn randStr
putStrLn randStr
如果我们对周围的上下文有更多了解,可能会更容易回答这个问题,但我会采取的方法是在任何需要的地方传递字符串,并在main
. 因此:
import Control.Monad
import System.Random
-- Some arbitrary functions
f :: String -> Int -> Int -> Int
f rstr x y = length rstr * x * y
-- This one doesn't depend on the random string
g :: Int -> Int
g x = x*x
h :: String -> String -> Int
h rstr str = sum . map fromEnum $ zipWith min rstr str
main :: IO ()
main = do
rstr <- randomString
putStr "The result is: "
print $ f rstr (g 17) (h rstr "other string")
randomString :: IO String
randomString = flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
这大概就是我会做的。
另一方面,如果你有很多这些函数,你可能会发现传递rstr
给所有这些函数很笨重。要对此进行Reader
抽象,您可以使用monad ; 类型的值Reader r a
——或者更一般地说,类型的值MonadReader r m => m a
——能够在顶层传递一次ask
类型的值。r
那会给你:
{-# LANGUAGE FlexibleContexts #-}
import Control.Applicative
import Control.Monad.Reader
import System.Random
f :: MonadReader String m => Int -> Int -> m Int
f x y = do
rstr <- ask
return $ length rstr * x * y
g :: Int -> Int
g x = x*x
h :: MonadReader String m => String -> m Int
h str = do
rstr <- ask
return . sum . map fromEnum $ zipWith min rstr str
main :: IO ()
main = do
rstr <- randomString
putStr "The result is: "
print $ runReader (f (g 17) =<< h "other string") rstr
randomString :: IO String
randomString = flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
(实际上,由于(r ->)
是 的一个实例MonadReader r
,所以上面的函数可以被视为具有 typef :: Int -> Int -> String -> Int
等,并且您可以省略对runReader
(和 remove FlexibleContexts
)的调用——您构建的一元计算将只是 type String -> Int
。但我可能不会打扰。)
另一种方法,可能是不必要地使用语言扩展(我当然更喜欢上面的两种方法),是使用隐式参数,它是一个动态传递并反映在类型中的变量(有点像MonadReader String m
约束)。看起来像这样:
{-# LANGUAGE ImplicitParams #-}
import Control.Monad
import System.Random
f :: (?rstr :: String) => Int -> Int -> Int
f x y = length ?rstr * x * y
g :: Int -> Int
g x = x*x
h :: (?rstr :: String) => String -> Int
h str = sum . map fromEnum $ zipWith min ?rstr str
main :: IO ()
main = do
rstr <- randomString
let ?rstr = rstr
putStr "The result is: "
print $ f (g 17) (h "other string")
randomString :: IO String
randomString = flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
现在。我必须承认,你可以在最高层做这些事情。例如,有一个标准的 hack 允许使用unsafePerformIO
来获取顶级IORef
s;并且 Template Haskell 将允许您在编译时运行一次 IO 操作并嵌入结果。但我会避免这两种方法。为什么?好吧,从根本上说,关于“纯”是指“完全由语法确定/不会在程序的任何运行中改变”(我喜欢的解释),还是意味着“在这次运行中不会改变”,存在一些争论的节目。” 作为这导致的问题的一个例子:包Hashable
,在某一时刻,从固定盐切换到随机盐。,并将错误引入以前工作的代码中。该软件包已退后,现在允许用户通过环境变量选择加入此行为,默认为运行间纯度。
也就是说,这里是如何使用您提到的两种方法unsafePerformIO
和 Template Haskell 来获取顶级随机数据 - 以及为什么,除了对运行间纯度的担忧之外,我不会使用这些技术。(这是我能想到的仅有的两种技术。)
所谓的unsafePerformIO
hack非常脆弱。它依赖于某些未执行的优化,通常不是一种受欢迎的方法。这样做看起来像这样:
import Control.Monad
import System.Random
import System.IO.Unsafe
unsafeConstantRandomString :: String
unsafeConstantRandomString = unsafePerformIO $
flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32)
{-# NOINLINE unsafeConstantRandomString #-}
unsafe
不过,说真的,看看上面代码中使用了多少这个词?那是因为使用unsafePerformIO
会咬你,除非你真的知道你在做什么,甚至可能。即使unsafePerformIO
不直接咬你,至少 GHC 的作者会说它可能不值得为此使用(参见标题为“犯罪不付钱”的部分)。 不要这样做。
为此使用 Template Haskell 就像使用核弹头杀死蚊子一样。一个丑陋的核弹头,启动。该方法如下所示:
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad
import System.Random
import Language.Haskell.TH
thConstantRandomString :: String
thConstantRandomString = $(fmap (LitE . StringL) . runIO $
flip replicateM (randomRIO (' ','~')) =<< randomRIO (1,32))
另请注意,在 Template Haskell 版本中,您不能将随机字符串创建功能抽象为randomString :: IO String
同一模块中的单独值,否则您将违反阶段限制。不过,与unsafePerformIO
黑客不同,它是安全的;至少,安全模数上述关于运行间纯度的担忧。
正如文档所说,在这种特殊情况下使用unsafeperformIO
似乎很好:
为了安全起见,IO 计算应该没有副作用并且独立于其环境。
我们不担心newStdGen
.
import System.Random
import System.IO.Unsafe
randomStr :: String
randomStr = take 10 $ randomRs ('a','z') $ unsafePerformIO newStdGen
main = do
putStrLn randomStr
putStrLn randomStr
生成随机数IO
并不意味着下游函数必须使用IO
.
这是一个依赖于 type 值的纯函数示例A
:
f :: A -> B
...这是一个IO
生成的动作A
:
io :: IO A
我不必修改f
即可使用IO
. 相反,我使用fmap
:
fmap f io :: IO B
这正是仿函数应该解决的问题:将态射提升到包装值上,以便不需要修改态射。
import System.Random
main = do
gen <- newStdGen
let str = take 10 $ randomRs ('a','z') gen
putStrLn str
putStrLn $ (reverse . (take 3)) str
这会生成一个只有小写字母的十个字符长的字符串。此代码在 IO monad 中,但 str 是纯的,它可以传递给纯函数。如果没有 IO Monad,您将无法获得随机的东西。你可以做一个 unsafePerformIO 但我真的不明白为什么。如果您总是想要相同的值,则可以传递 str 值。如果您查看我的代码的最后一行,您可以看到我有一个对字符串进行操作的纯函数,但是因为我想看到它,所以我调用putStrLn
它返回一个空的 IO 操作。
编辑:或者这可能是 Reader Monad 的地方
对于字符串、数字和其他:
import System.Random ( newStdGen, randomRs, randomRIO )
main :: IO ()
main = do
s <- randomString 8 ""
putStrLn s
randomString :: Integer -> String -> IO String
randomString 0 str = return str
randomString size str = do
g <- newStdGen
t <- randomRIO ( 0, 2 )
let s = take 1 $ randomRs ( range t ) g
randomString ( size - 1 ) ( str ++ s )
where
range :: Integer -> ( Char, Char )
range i
| i == 0 = ('0', '9')
| i == 1 = ('A', 'Z')
| otherwise = ('a', 'z')