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

Reb*_*bin 0 haskell

我正在阅读"了解你一个Haskell"中有关随机性的一些样本,我对此感到有些困惑newStdGen.根据文档,它更新内部变量,以便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)
Run Code Online (Sandbox Code Playgroud)
?> main
dgnatnxgvammlgxgeumk
rrxxnwupmbnxpxrkofjw
Run Code Online (Sandbox Code Playgroud)

我很欣赏getStdGenis 的类型IO StdGen,它是"一个IO动作,当执行时,将产生一个StdGen",以及绑定操作

gen <- getStdGen
Run Code Online (Sandbox Code Playgroud)

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

我要感谢我对我的理由的确认或驳斥以及任何进一步的澄清,这会使这看起来不那么怪异.

编辑:

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

Dan*_*ner 5

参照透明度表示您可以在不改变程序含义的情况下用其定义替换变量(反之亦然).("引用" - 变量名称 - 是"透明的" - 你无法分辨你是在查看引用还是它背后的东西.)

在这种情况下,Haskell的模块系统隐藏了实现的各个部分,特别是theStdGen :: IORef StdGen,因此我们无法从技术上取代newStdGen其实现.但这不是对参考透明度的"深刻"违反; 如果模块的这一部分暴露出来,我们确实可以取而代之的newStdGen是它的实现,即

atomicModifyIORef' theStdGen split
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

比较这个Haskell片段:

theStdGen :: IORef StdGen
theStdGen  = unsafePerformIO $ do
   rng <- mkStdRNG 0
   newIORef rng
Run Code Online (Sandbox Code Playgroud)

如果我们要用theStdGen它的定义来取代,那改变程序的含义 - 而不是重用现有的IORef,这将导致IORef创建新的,并且共享将丢失.这是unsafein 的意思(的一部分)unsafePerformIO:使用的东西unsafePerformIO可能不是参考透明的!