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

The accepted proposal to add Random API to the standard library defines a single random source in the standard library. Users can create their own random sources by writing types that conform to the RandomNumberGenerator protocol. For convenience, the proposal includes a single generator as part of the standard library. The type is a struct currently named Random. The initializer is private, so all usage of the Random stdlib generator from clients must access the instance via a static property: Random.default.

I am recommending an amendment to the proposal which provides a better name for this stdlib type. I propose that the stdlib generator should be called DefaultRandomNumberGenerator. This name makes it plain that it is a default implementation of the RandomNumberGenerator protocol. Random is far too generic and does not allude to the fact that other sources of randomness can (and should) exist. The name DefaultRandomNumberGenerator also indicates that it is merely the system default, and developers should consider whether other generators would be more appropriate for their particular case.

This naming pattern has precedence in other standard library API. A default implementation of a Collection.Indices uses a type called DefaultIndices, for example.

This proposed name-change would also require a change to the static property to prevent duplication of words. I propose DefaultRandomNumberGenerator.shared.

Note that whilst DefaultRandomNumberGenerator.shared as a term is significantly longer than Random.default, the number of times an average user will type this is low. It is customary for random element accessors to provide two forms; one that includes an argument to supply a generator and another that implicitly uses the default stdlib generator. At an actual call site, a user of Swift will rarely actually include a definition of DefaultRandomNumberGenerator in their code.

The main impact this change will have is on how the standard library defines its extensions, and what Swift programmers digging around in autocomplete will see.

For example, this code in the standard library:

extension Collection {
  public func randomElement<T: RandomNumberGenerator>(
    using generator: inout T
  ) -> Element?
  
  public func randomElement() -> Element? {
    return randomElement(using: &Random.default)
  }
}

will become:

extension Collection {
  public func randomElement<T: RandomNumberGenerator>(
    using generator: inout T
  ) -> Element?
  
  public func randomElement() -> Element? {
    return randomElement(using: &DefaultRandomNumberGenerator.shared)
  }
}

For most users of the API, there is unlikely to be any impact. People will still call collection.randomElement() and collection.randomElement(using: otherRNG) without ever paying the penalty of the extra characters, as the use of the stdlib generator is implicit and will rarely be typed explicitly.

However, in the times when the name of the type does appear, like in autocomplete lists, the intent of the DefaultRandomNumberGenerator type is much clearer than the originally proposed, opaque, Random name.

What do others think about this amendment?

(This commentary was previously brought up in the original proposal’s review thread. A formal decision on the naming was overlooked. Per Ben Cohen’s request, I am proposing the change as an amendment to the accepted proposal.)

13 Likes

+1, I prefer DefaultRandomNumberGenerator.shared but I also have another suggestion.

When someone is using their own generator for reproducible results, they might forget the using: &generator argument (in perhaps just one of many API calls).

There’s a similar problem with Swift.print, where forgetting the to: &textOutputStream argument will unexpectedly write to the standard output.

I’m wondering if it’s possible to set the default generator for an entire source file. This could be similar to the existing typealias IntegerLiteralType = Double but I don’t know how that works, maybe it requires special compiler support?

I’m a +1 on this new name, too. It communicates very clearly what the purpose is, and like you say it will rarely be typed, so the additional length isn’t really a concern.

I do not think this is more discoverable or more readable. I think I would much rather have a system to getting default values for any type as in C# default() ala default(RandomNumberGenerator) but since we do know have that then it makes sence for this to live in Random since random implements the RandomNumberGenerator protocol

I do not like the idea of a static/singleton like shared unless it is necessary (as in, would this be a class then or have reference semantics?) but in this case it seem to be just for the sake of longer naming.

Now, that being said, I do not see a problem having this in addition to Random.default

3 Likes

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.