`newStdGen`打破了`getStdGen`的引用透明度?

时间:2017-12-25 01:34:28

标签: haskell

我正在阅读一些samples about randomness in "Learn You a Haskell",我对newStdGen感到有些困惑。根据{{​​3}},它会更新内部变量,以便getStdGen在下次调用时产生不同的内容。这听起来不像是参考透明度。实际上,以下程序显示进一步调用getStdGen会产生不同的结果,至少在单变量绑定到变量时:

import System.Random
main = do
  gen <- getStdGen
  putStrLn $ take 20 ( randomRs ('a','z') gen)
  newStdGen
  gen <- getStdGen
  putStrLn $ take 20 ( randomRs ('a','z') gen)
λ> main
dgnatnxgvammlgxgeumk
rrxxnwupmbnxpxrkofjw

我感谢getStdGen的类型是IO StdGen,这是“一个IO动作,执行时会生成StdGen”,并且绑定操作

gen <- getStdGen

将每个StdGen绑定到(顺序本地)变量gen。也许解决方案很简单,bind语法中隐含的monadic do操作序列可确保两个gen变量不同。

我很感激对我的推理的确认或驳斥以及任何进一步的澄清会使这看起来不那么怪异。

修改

评论帮助我意识到,我更深层次的困惑涉及使用相同的参数(即无)调用getStdGen两次并获得不同结果的外观。获得相同参数的相同结果的属性不称为参照透明度,因此我的问题具有误导性。幕后发生的事情类似于State中发生的事情,正如user2407038所指出的那样:结果是隐藏状态的函数,它们在绑定操作中一起被线程化。

1 个答案:

答案 0 :(得分:5)

参照透明度表示您可以在不改变程序含义的情况下用其定义替换变量(反之亦然)。 (&#34;参考&#34; - 变量名称 - &#34;透明&#34; - 您无法判断您是否正在查看引用或其后面的内容。 )

在这种情况下,Haskell的模块系统隐藏了实现的各个部分,特别是theStdGen :: IORef StdGen,因此我们无法技术替换newStdGen与其实施。但这不是一个很深的&#34;违反参考透明度;如果暴露了模块的这一部分,我们确实可以将newStdGen替换为其实现,即

atomicModifyIORef' theStdGen split

并且程序的含义不会改变。所以这里的引用透明度没有问题。

比较,例如,这个C片段:

int referent[4] = {0, 1, 2, 3};
int *x = referent, *y = x;
x++;
printf("%d\n", *y);

如果我们将y替换为其定义x,我们将获得一个具有不同含义的程序!

比较这个Haskell片段:

theStdGen :: IORef StdGen
theStdGen  = unsafePerformIO $ do
   rng <- mkStdRNG 0
   newIORef rng

如果我们要按其定义替换theStdGen更改程序的含义 - 而不是重用现有的IORef,这将导致新鲜要创建IORef,共享将会丢失。这是unsafeunsafePerformIO含义的(部分):使用unsafePerformIO的内容可能不会在引用中透明!