已有生成随机数函数生成扩展_如何创建没有副作用的随机数生成器函数

已有生成随机数函数生成扩展

Must you be thinking about this title? Is that even possible? The random generator has been illustrated as an example of side-effects. Each time when we call random.nextInt twice, the value will not be the same. We don't know nextInt besides, we know that it generates random values. Let's look at a regular imperative API that relies on side effects:

您是否正在考虑这个标题? 那有可能吗? 已经举例说明了随机发生器作为副作用的例子。 每次我们两次调用random.nextInt ,该值都将不同。 此外,我们不知道nextInt ,我们知道它会生成随机值。 让我们看看依赖于副作用的常规命令式API:

val random = new scala.util.Random


rng.nextInt


rng.nextInt // this will produce different value

In this article, we will first discuss why we want a random generator without side effects. Then, we change this API so that it is referentially transparent. By the end of this, you will know the gotcha on how to create a functional imperative program with having pure states.

在本文中,我们将首先讨论为什么要一个没有副作用的随机生成器。 然后,我们更改此API,使其具有参照透明性。 到此为止,您将了解如何创建具有纯状态的功能命令程序的技巧。

为什么还要打扰? (Why even bother?)

We know that a random generator will always generate random values. However, this becomes trouble when we incorporate “randomness” into our program.

我们知道随机生成器将始终生成随机值。 但是,当我们将“随机性”纳入我们的程序时,这将成为麻烦。

Imagine you need were developing a program that relies on random numbers to do AB Test two different features. However, you encounter trouble when doing unit-test. How do you unit test this application, since it creates a side effect?

想象一下,您需要开发一个依赖于随机数的程序来进行AB测试两个不同的功能。 但是,进行单元测试时会遇到麻烦。 由于该应用程序会产生副作用,因此如何对其进行单元测试?

Besides, you might be thinking, any sort of bugs in the concurrent environment will be tough to reproduce. Since you don’t know how to reproduce the problem, you have a hard time explaining to your senior management or manager why there is a considerable failure in your system at 2 AM on Tuesday.

此外,您可能会认为,并发环境中的任何类型的错误都很难重现。 由于您不知道如何重现该问题,因此很难向您的高级管理人员或经理解释为什么周二凌晨2点系统会出现相当大的故障。

How do you create randomness in your program that can be easily unit-test and, most importantly, able to reproduce bugs reliably?

您如何在程序中创建可以轻松进行单元测试并且最重要的是能够可靠地重现错误的随机性?

了解随机性 (Understanding Randomness)

Before we tackle the problem, we have to understand how do “randomness” generated. Let’s revisit the regular random generator API from the standard scala library:

在解决问题之前,我们必须了解“随机性”是如何产生的。 让我们从标准scala库中重新访问常规的随机生成器API:

val random = new scala.util.Random


rng.nextInt


rng.nextInt // this will produce different value

We don’t know how nextInt the function is implemented. However, we know that when we call random.nextInt, there is some internal state inside there that gets updated. Once it updates, it returns a new value. Because the update of the country is performed as side-effects, the function is not referentially transparent.

我们不知道nextInt函数是如何实现的。 但是,我们知道,当我们调用random.nextInt ,其中的一些内部状态会被更新。 更新后,它将返回一个新值。 由于国家/地区的更新是作为副作用执行的,因此该功能不是参照透明的。

From the observation above, we can deduce two facts. First, there is no “pure” randomness in programs. They are all functions that produce “pseudo” random to the caller. The reason why the caller thinks that it is random is that they don’t expose their internal state to the caller. It gives the caller a number that they cannot reproduce. Thus, it is “random”. Second, we can roughly understand how nextInt is implemented. When we instantiate the random number, it provides an internal seed value to the random generator. Then, each time we evoke nextInt, it will use some algorithms, updates its internal state, and return a new value.

从上面的观察,我们可以推断出两个事实。 首先,程序中没有“纯粹的”随机性。 它们都是对调用者随机产生“伪”的函数。 呼叫者认为它是随机的,原因是他们没有向呼叫者公开其内部状态。 它为呼叫者提供了一个他们无法复制的号码。 因此,这是“随机的”。 其次,我们可以大致了解nextInt的实现方式。 当我们实例化random数时,它为随机生成器提供了内部种子值。 然后,每次我们调用nextInt ,它将使用一些算法,更新其内部状态,并返回一个新值。

告诉我如何解决这个问题! (Show me how to solve this!)

Yes, we will get there! But I want to show you how we can derive to that purely functional state so that you can implement this derivation not only for this particular problem but also for other issues with a state within.

是的,我们会到达那里! 但我想向您展示如何才能推导出该纯函数状态,以便您不仅可以针对此特定问题,而且还可以针对某个内部状态的其他问题实施此推导。

We know that each time we call a random generator, we can provide its random generator with a seed value. That seed value will use some algorithm to compute from the current state to the next state. Therefore, to reproduce the “same” generator, we need to have the same seed with that same state to reproduce the same future state.

我们知道,每次调用随机生成器时,我们都可以为其随机生成器提供种子值。 该种子值将使用某种算法从当前状态计算到下一个状态。 因此,要再现“相同”的生成器,我们需要具有相同状态的相同种子来再现相同的未来状态。

However, in the regular random number generator, once the next state is generated, the previous state is destroyed. This becomes hard to produce the same result. Since we are given seed value, we need to keep track of how many times the nextInt or nextDouble is called to reproduce the same result.

但是,在常规随机数生成器中,一旦生成了下一个状态,就破坏了前一个状态。 这很难产生相同的结果。 由于给定了种子值,因此我们需要跟踪调用nextIntnextDouble再现相同结果的次数。

Fortunately, there is a way to make the random generator pure without needing to keep track of the functions’ counts. The key to recovering referential transparency is to make updating the state explicitly. Don’t update the state as a side-effect, simply return the new state along with the result that we are generating.

幸运的是,有一种方法可以使随机生成器变为纯函数,而无需跟踪函数的计数。 恢复引用透明性的关键是明确地更新状态 。 不要将状态更新为副作用,只需返回新状态以及我们生成的结果即可。

In the example of a random generator, instead of mutating the state inside the function in place, we return a new state and the “random” number generated back to the caller.

在随机生成的例子,而不是突变到位函数内部的状态,我们返回一个新的状态,并产生返回给调用者的“随机”数。

case class Random(seed:Int) {
  def nextInt:(Int, Random) = {
    val rng = new scala.util.Random(seed)
    val res = rng.nextInt
    val newSeed = Random(seed+1)
    (res, newSeed)
  } 
}

Let’s re-create a random generator. For this random generator, we will build on top of scala random numbers util by passing in the seed value to scala.util.Random(seed). Then, compute the next seed value, and return the new random generator to the caller.

让我们重新创建一个随机生成器。 对于此随机数生成器,我们将种子值传递到scala.util.Random(seed)从而在scala随机数util的基础上构建。 然后,计算下一个种子值,然后将新的随机数生成器返回给调用方。

We can run this random generator by calling the example below:

我们可以通过调用以下示例来运行此随机数生成器:

val rng = Random(1)
val (newValue, nextRandom) = rng.nextInt
val (newValue1, nextRandom1) = nextRandom.nextInt


println(s"new value ${newValue}. newValue1: ${newValue1}.")




val (sameValue1, sameNextRandom1) = nextRandom.nextInt
println(s"newValue1: ${newValue1}. sameValue1: ${sameValue1}")

We return the new state with the old state unmodified. We separate the new state’s computation with communicating this new state to the rest of the program. There is no internal state memory being used. We are merely giving the next state to the caller, and let the caller have complete control to decide what to do with that new state.

我们返回未修改的旧状态的新状态。 我们的新状态的计算与通信这个新状态到程序的其余部分分开。 没有使用内部状态存储器。 我们仅将下一个状态提供给调用方,并让调用方具有完全控制权来决定如何处理该新状态。

We can use this pattern in a lot of programs that contain a global or mutable state. The key to making a simple state API is to return that new state to the caller and let the caller have full control over the state.

我们可以在许多包含全局或可变状态的程序中使用此模式。 要制作一个简单的状态API的关键是新状态返回给调用者,让来电者有对国家的完全控制。

I will give one more example for a class with an internal state, and how you can refactor it to be pure.

对于具有内部状态的类,我将再给出一个示例,以及如何将其重构为纯净的。

Each time when you see a class that has an internal state like this:

每次您看到一个具有如下内部状态的类时:

class Foo {
  private var map : FooState = ???
  def put: Bar
  def write: Int
}

Suppose that put and write will mutate the map state in some ways.

假设putwrite将以某种方式使map状态发生变化。

You can translate the above class by making explicit the transition from one state to the next by changing to this:

您可以通过更改为以下内容,通过从一种状态到另一种状态的显式转换,来翻译上述类:

trait Foo {
  def put: (Bar, FooState)
  def write: (Int, FooState)
}

Now you notice that if you want to do sequence a computation in a practical way, it will be awkward and tedious. Therefore, we can abstract these computations into a State monad. However, that will be a post for another day.

现在您注意到,如果您想以一种实用的方式对计算进行排序,那么它将很麻烦且乏味。 因此,我们可以将这些计算抽象为State monad 。 但是,这将是另一天的帖子。

主要外卖 (Main Takeaway)

  • The key to recovering referential transparency is to separate the concerns of the next state’s computation with the communication of that state to the rest of the program.

    恢复引用透明性的关键是将下一状态的计算与该状态与程序的其余部分的通信分开。
  • By making stateful API pure, we transition from one state to the next explicit to the caller and delegate that decision to the caller.

    通过使有状态API变得纯净,我们可以将一种状态转换为显式的另一种状态,然后再将该决定委托给调用方。

Thanks for reading! If you enjoy this post, feel free to subscribe to my newsletter to get notified on essays about Career in Tech, interesting links, and content!

谢谢阅读! 如果您喜欢这篇文章,请随时 订阅 我的新闻通讯,以获取有关技术职业,有趣的链接和内容的文章的通知!

You can follow me also follow me on Medium for more posts like this.

您可以在媒体上关注我,也可以关注我,以获取更多类似这样的帖子。

Originally published at https://edward-huang.com.

最初在 https://edward-huang.com上 发布

翻译自: https://medium.com/the-innovation/how-to-create-a-random-number-generator-function-without-side-effects-99df2b74d3fc

已有生成随机数函数生成扩展

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值