SE-0202 Amendment: Naming of the standard library random number generator

I echo this sentiment.

Thanks for writing this up!

I’m a little bias, so hopefully none of my arguments show that :wink:

In general, I’m not a fan of the rename. I actually disagree regarding that fact that the default generator will be rarely explicitly used. I think with the introduction of this new API that many more people will be exploring and experimenting with ways to incorporate this into their own APIs. I’m actually writing up another proposal to further increase functionality in RandomNumberGenerator which would require that some users have to interact with the generator itself, rather than a method take a generator. I don’t want to go too off topic here, but I imagine many others will write extensions onto RandomNumberGenerator to further increase functionality to fit their needs. Example:

let buffer = ...
Random.default.fillBuffer(buffer)
// vs
DefaultRandomNumberGenerator.shared.fillBuffer(buffer)

I think operations like these prove to show that there is an unnecessary verbosity in what it’s performing.

I agree with this line of thinking in that a singleton called shared is generally attached to reference semantic types (which this does not have). I think the only great spelling for this is actually default, but this would look something like DefaultRandomNumberGenerator.default.

Now, I will agree that Random on its own is ambiguous, but I did this for a reason. In the future if Swift is to ever add a deterministic RNG in the stdlib, it could be shipped under something like Random.xorshift128Plus as a globally initialized instance of Random.Xorshift128Plus (seeded from Random.default of course, along with having the ability to initialize your own instance of this type of course). Now this is thinking really far into the future, but this flexibility with Random allows us to do some really cool things if Swift ever reaches a point where it needs it (such as adding new generators with obvious names that would break a lot of code if not namespaced under Random).

I don’t entirely agree with the argument that this has precedence in the stdlib. The only type in the stdlib with the prefix Default is DefaultIndicies (at least from my findings) and that type doesn’t conform to some magic protocol called Indicies.

Overall, I don’t really see a huge advantage using DefaultRandomNumberGenerator.shared over Random.default rather than being overly clear about what it is. I think many people will want to use this new API that they will eventually come across DefaultRandomNumberGenerator.shared and feel a little intimidated to continue using it.

I apologize if anything I said was a little biased!

7 Likes

Hello,

Reading @Alejandro’s answer, it looks like Random is a namespace, designed to welcome some future random-related APIs, including generators: Random.xorshift128Plus, etc.

Couldn’t we honor this design decision and envisioned use cases, and in the same time address the concern expressed by this pitch? Renaming Random.default to Random.defaultGenerator could work. The type of Random.defaultGenerator would be DefaultRandomNumberGenerator. Random itself would be an empty enum, the only namespace-like construct we have so far.

12 Likes

The current proposal establishes a pattern of adding random-related convenience functions as extensions to instances or types. So Random.default.fillBuffer() would be expressed as something like Buffer.randomBytes(byteCount:using:) at the type level or buffer.randomizeBytes(using:) on an instance. The using argument is optional, defaulting to the stdlib RNG’s source. Why would this need to be inverted?

The idea that Random can act as a namespace is neat, but this intent is not described in the original proposal at all. If namespacing these types is worthwhile, then even so I would vote for some syntactic amendments. Random should be a enum (or a formal namespace declaration when we have one) and offer a static method like default which vends a DefaultRandomNumberGenerator. It doesn’t really make sense to have a Random struct type also be the namespace.

If brevity is deemed a concern by many, DefaultRNG is still vastly superior to Random in my opinion. The abbreviation refers back to the name of the parent protocol, so I think the logical hierarchy is generally maintained. Still, I laid out my reasoning as to why I think character count is not of primary importance here.

(FWIW the idea that the standard library should host namespaces at all, with the expectation for others to extend it, is something that deserves discussion and not taken at face value. This isn’t a common stdlib pattern today. When I read the original proposal, I was thinking that Xorshift128RNG (eg) and DefaultRandomNumberGenerator would simply sit alongside each other as top-level types.)

1 Like

The real answer, of course, is that we should allow protocol meta-types to have members.

Then you really could write RandomNumberGenerator.default.

6 Likes

But the real answer won’t kill this current amendment pitch, will it?

I said could :wink:
The current design states that Random is the default generator, rather than some empty namespace. Ben is suggesting that this name is:

There is a term-of-art justification for this name as well. In many languages, such as Java, Scala, Kotlin, D, C# (.NET), Ruby, this spelling is consistently used as the stdlib RNG. Now I understand Swift isn't one of these other languages, but I think it's important to evaluate what other languages have done. Granted these languages get most (if not all?) of their random functionality from this Random type, but it still acts as the stdlib RNG that these languages use. I'm still under the opinion that this spelling still expresses that it is the stdlib RNG. I think in combination with the spelling of the static property default this further explains that this is obviously the stdlib RNG. As for not alluding to the fact that other generators can exist, I disagree entirely. This would be true if we did not come up with something like RandomNumberGenerator, but we made a public utility that many others can build from in addition to the stdlib RNG.

These extensions that users are creating also implies that they are allowing a default method to utilize Random.default which is why I believe this API will be used more commonly than what you think. I agree that the proposal establishes this pattern, but we would still need this method on generators as well (for one is to really juice the performance out of Random.default). So again, I disagree that this is an API that will be rarely used.

The proposal doesn't mention this because Random doesn't necessarily need to attribute itself with the namespacing idea. It needs to be the stdlib RNG. If the discussion for extended random behaviors come up at a future date, this is an alternative design that could be used. Such design would need many months of Swift bikeshedding of course :wink:

And they most certainly could. I was simply stating that the current design could allow for such alternative designs.

At the end of the day, I think this comes down to subjective opinions about how to spell an API. During the pitch phase and most of the review phase this idea that the naming for Random.default would cause active harm to users never came up (maybe my memory is failing me). Maybe it was overlooked, or maybe some didn't care, but I don't believe that this spelling would cause any active harm. Again, I'm not seeing how DefaultRandomNumberGenerator.shared is vastly superior to what is already accepted besides screaming the fact that it's the default RNG. But even then, I think Random does this very well because of whats precedent.

2 Likes

I rather like this specific spelling for the call-site, but I agree that DefaultRandomNumberGenerator is a better name for the specific struct that provides the default implementation. It does seem important to notate that this specific implementation isn’t the only source of randomness that could exist.

I really like the sound of using Random as a namespace. It seems like the best compromise between the two concerns, and serves as a great springboard for other random-related APIs going forward.

enum Random {
    static var defaultGenerator: DefaultRandomNumberGenerator {
        return DefaultRandomNumberGenerator.shared
    }
}

As an aside, it seems like DefaultRandomNumberGenerator.shared would have to exist alongside Random.defaultGenerator, since the initializer for DefaultRandomNumberGenerator is otherwise private.

Exact. Still, after SE-0202, we have an identifier, Random, that’s half a namespace, half a concrete type, with the caveats expressed in the OP.

I wish your full answer would have entered the Future Direction chapter of https://github.com/apple/swift-evolution/blob/master/proposals/0202-random-unification.md.

Because now it looks that what has a been formally accepted is a bad fit for your own envisioned future. I wish the namespacing of Random had been part of the pitch and review of SE-0202.

2 Likes

I’m not suggesting the current name is ‘actively harmful’, but given the proposal isn’t implemented yet in public Swift I think there is a much lower bar needed to critique it and change it to something better. DefaultRandomNumberGenerator.shared is better, and I believe we should roll with that.

I brought up the same opinion in the review phase and it seemed to gain some community acceptance; an administrative error has led to this amendment thread being necessary as explained at the bottom of the original post.

And just to agree with what others saying here, I too believe the best choice here would be to use a future ability to nest types and property definitions inside protocols. However, this proposed name change is forwards compatible. DefaultRandomNumberGenerator.shared would be the formal definition of the type, and RandomNumberGenerator.default would be a property on the protocol.

Similarly, a formal Random namespace (either via a case-less enum or language feature) would also still require the underlying default generator to be named something; i.e:

enum Random {
   static var defaultGenerator: DefaultRandomNumberGenerator { return ... }
}

In summary, Random.default is not a terrible name but I believe obvious alternatives exist that provide superior clarity and consistency with the standard library. And there is no source compatibility argument to be had here.

2 Likes

On the subject of naming, the proposed term of art in C++ is Uniform Random Bit Generator (URBG):

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0346r1.pdf

The original terms were uniform random number generator (e.g. Mersenne Twister) and random number distribution (e.g. Uniform, Bernoulli, Poisson):

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3551.pdf

The proposed renaming was to make it clear that a generator shouldn’t be used as if it were a distribution. Maybe the Swift APIs don’t have this issue.

1 Like

I’m not sure I can take this naming seriously. It’s overly verbose, and I’m really not convinced .shared makes sense for this.

Also, this makes using the default generator much longer, and pretty annoying.

Whilst I think Random could be improved, I do not think that this is the right choice, and I struggle to see a better choice

2 Likes

I'm not too concerned about this. If somebody comes along and creates a new RandomNumberGenerator implementation, they would surely want to use a descriptive name that distuishes it from the "default" Random, e.g. DiceRollBasedRandom, MockRandom, etc.

Have the "default" Random be plain and simple is ideal for discoverability and code length.

11 Likes

I do think that the namespace and the default implementation should be split up. I think it should probably be arranged more like this:

public enum Random {
    public struct DefaultRNG: RandomNumberGenerator {
        public mutating func next() { ... }
        ...etc...
    }
    public static let defaultRng = DefaultRNG()
}

I don’t think there’s a way to scope the initializer to only be visible inside the containing enum, so it probably needs to be fileprivate.

I’m not especially attached to the names, but I do think that DefaultRandomNumberGenerator.shared is both wrong (as the default generator is not going to be a reference type) and overly verbose.

1 Like

Alternative names:

The semantics of its usage are shared though, right? The default RNG uses a shared source of randomness under the covers. I think the actual type of the object is irrelevant.

I’ll throw in DefaultRNG here too. It’s simply an abbreviated form of my originally proposed name, but it still conveys what’s important whilst saving characters (which has been a common criticism in this thread even given the fact writing out the name of the actual type is likely a rare occurrence.)

The complaint is never about writing, it’s about mental effort required to parse it each and every time.

1 Like

True... I think the discussion about how to actually use the API got sidelined to much by implementation details -- but I hope that the high-level APIs that have to be created yet play well with the accepted proposal.

Imho it doesn't have this exact issue, but a less clean design than C++:
Because there are methods to choose a random number in a given range, the biased randomValue % (upperBound - lowerBound) transformation is less likely to be used.
But some functionality depends on a hidden transformation (which probably maps to a uniform distribution ;-), so it can look like the generator is a arbitrary distribution.

Hopefully ;-) -- I like the pattern of a generator parameter whose default value is Random.default, and I expect many high-level APIs to utilize it.

I don't think it's good to use abbreviations, even if it's unlikely that someone who doesn't know the meaning of RNG needs that type.

I think those belong to a Random module:
It’s good when the stdlib is tiny and only has rudimentary support, the topic is large enough to fill a separate library.

1 Like