SE-0202 Amendment Proposal: Rename Random to DefaultRandomNumberGenerator

To answer the original question, if .shared isn't suitable, would .current be any better?

You could change next() and next(upperBound:) into subscripts, to thwart autocomplete!

public protocol RandomNumberGenerator {
  subscript(_: UnboundedRange) -> UInt64 { mutating get }
}

extension RandomNumberGenerator {
  public subscript<T>(_: UnboundedRange) -> T
  where T: FixedWidthInteger & UnsignedInteger { mutating get }

  public subscript<T>(range: PartialRangeUpTo<T>) -> T
  where T: FixedWidthInteger & UnsignedInteger { mutating get }
}

Ben, I am quite saddened seeing the security by obscurity in the API naming philosophy you presented here. All code can be used incorrectly and we should not resort to naming things with the aim of preventing misuse by the uninformed programmer. Correct use of random API introduced by SE-0202 must be clearly documented and the type Random is the right place to do just that.

Clearly, let i = Int(Random.default.next() % n) is anti-pattern that among other things SE-0202 seeks to correct by providing higher level API that handles this use-case while avoiding the (minor) modulo bias issue. However that anti-pattern is widespread in all languages and it persists in programming culture. It’s an educational issue that can be improved by spreading knowledge through:

  • clear documentation,
  • use of code linters,
  • code-review.

It cannot be solved by bastardication of clear names for hypothetical defensive purposes.

3 Likes

I disagree. Random.default is not a good name because it is misleading. You argument can be used against DefaultRandomNumberGenerator, but can’t justify Random.default.

I agree that documentations are important. But sometimes people use text editors (other than Xcode) with limited documentation support. We can’t solely rely on documentations to remind people.

It's not a question of obscurity, but one of avoiding misleading naming.

Random.default screams "I am the main way you generate random numbers", at least to me. But this is not true. It is very much not the thing most users should use. But it is there, in their faces, begging via its name to be used. This is bad. What's more, by claiming the name Random, the default random generator implies way more than it should regarding what it is, which is a low-level implementation detail most users would never need to be aware of.

I am sympathetic (if, personally, not yet convinced) towards the need for a namespace for random-related things. But Random is not that namespace. It is a concrete type. A proposal for an actual namespace, intended to be a place for other libraries to put their types, should be considered as a separate proposal if need be. It is not a justification for keeping the name of the current type.

Regarding addressing the problem via clear documentation: this strategy has failed, emphatically, to solve the problem in the case of arc4random, which explicitly documents that using it in conjunction with % is a bug and that you should use arc4random_uniform instead.

4 Likes

Yes we can. I don’t see what the ide or editor has to do with documentation. Docs are normale in a pdf reader or Webbrowser.

It’s not the duty of the language to enforce coding style - The solution is good documentation, not insane names to force people not to use them.

Welcome to swift-evolution. :wink: Don't worry, we still get things done in between the arguments about names.

4 Likes

I'm starting to lose faith in the quality of results after such discussions...

You argument can be used to argue that “JavaScript is a good language.” :wink:

Personally, I don’t think DefaultRandomNumberGenerator is a good name, but I think Random.default is a bad name.

Can we rename RandomNumberGenerator protocol to something else (e.g. RandomNumberGenerating)? Then we can add RandomNumberGenerator enum. Add RandomNumberGenerator.default with DefaultRandomNumberGenerator (or RandomNumberGenerator.Default) type.

I just want to add that I did not lavish in starting a separate amendment thread for a naming debate. I followed the Core Team’s wishes here on how to handle the admin of addressing the oversight.

Breaking this into two parts.

First, you need to move past default, which it isn't and shared, which it isn't, to a word or phrase that states what it is. So how about generator or even better generatorSource. (Just generator may encourage call-site use of next().)

Second, is there any time the blessed default generator would be used by third parties? I assume there is, so underscored names are out, right? You can consider variations of DefaultRandomNumberGenerator that are slightly shorter, like DefaultRandomGenerator or something like that.

return Self.random(in: range, using: &DefaultRandomGenerator.generatorSource)

Random is the type of the default random number generator, it is not a "namespace" (that any type can be said to be a namespace, depending on how you define the word namespace, doesn't change anything).

Looking at the generated interface and documentation:

public struct Random : RandomNumberGenerator {
    /// The default instance of the `Random` random number generator.
    public static var `default`: Random
    ...
}

It is clear that Random is a RandomNumberGenerator and it has a static property called default which is of type Random because it is "The default instance of the Random random number generator" exactly like its documentation says.

This thread will be confusing and unproductive if we cannot even agree that what is currently named Random is the type of the default RandomNumberGenerator.


I think it is important to note that for truly random, non-repeatable RNGs like Random it doesn't make sense to create instances, hence the private initializer and a single instance being available as a static default property of Random. But this is in contrast to PRNGs, for which it doesn't make sense to not being able to explicitly create instances.

So, the way I see it, the only reason why Random prevents initialization (by having its only init being private) is that it happens to be an RNG and not a PRNG (for good reasons). If Random had been a PRNG, it would have had a public initializer, probably one init(seed: UInt64) and one init() which would use a truly random seed.

My point is that the inability to create instances of Random should not be taken as a hint of it being kind of like a "namespace" for various random stuff, in addition to being the type of the default random number generator. I think it would be a mistake to have the same type taking on both these very different roles.

So if there is a need for this namespace-thing, then it should not be conflated with anything else (including the type of the default random number generator), and it should probably be called Random, because Random would be a good name for such a namespace-thing.

I agree with the core team and others that Random is not a good name for the type of a (default) random number generator, that it is misleading, and that it should be renamed to DefaultRandomNumberGenerator (or DefaultRNG or something else that says what it is), which would also free up the name Random for the hypothetical namespace-thing, if that turns out to be something that is really needed and sensible.

The documentation of DefaultRandomNumberGenerator should be clear up front about its intended use, or rather non-use, in the common case, so that users see why they never should have to write it out explicitly unless they really have to.

1 Like

As you have been quite concerned about performance: Did you compare random(in:) with next(upperBound:)?

Also, I think using something that is called RandomNumberGenerator to, well, generate random numbers, looks quite legit, and the same is true for DefaultRandomNumberGenerator.
Therefor, the aspect of making it harder to discover Random.default.next is about obscurity, because people needing something random probably won't prefix this term with Default.

If RandomNumberGenerators shouldn't be used to generate random numbers, it might be better to call them RandomBytesGenerator, and define next to return or fill a buffer.

If it's about the intended use of the default RNG then I'm only trying to interpret the intent as I perceive it from reading the proposal, the current implementation (code and docs) and posts from the core team.

People can of course use the default RNG as much as they like, but it's quite clear that eg

let rollOfTwoDice = Int.random(in: 1 ... 12)

is meant as being the preferred / recommended way to use the default RNG, over eg

let rollOfTwoDice: Int = Random.default.next(upperBound: 12) + 1

Side note: The latter doesn't compile for some reason, I get the error: "Argument passed to call that takes no arguments. EDIT: Ah! Unsigned, it has to be an unsigned and fixed width integer, so here is a working way to write it:

let rollOfTwoDice = Int(truncatingIfNeeded: Random.default.next(upperBound: 12 as UInt8) + 1)

If it's about the length of the name, I'd be fine with calling it eg DefRNG instead of DefaultRandomNumberGenerator.


I haven't done any performance testing of the Random API, yet. I was actually about to, but ended up running into other questions and issues (like this thread). But just to be clear, if in some use case, I needed high performance, and no crypto sec, then my first decision would be to not use the default random number generator, but instead implement a fast PRNG like SplitMix64 or Xoroshiro128Plus (and my suspicion is that I might be disappointed in the performance of some of the methods provided by the current Random API, perhaps even next(upperBound: )).

1 Like

If the goal of this rename was to discourage people from using Random.default then I don't see how DefaultRandomNumberGenerator is any better. Because if someone googles "generate random numbers in Swift" there's probably a high chance this comes up. If we really want to discourage people from using it in that common misused way the name shouldn't make mention of Number IMHO. Something like EntropySource.default could certainly dissuade many from using it. As I don't think anyone will be encouraged to write EntropySource.default.next() % n

4 Likes

I found the performance of UInt64.random(in: 0 ..< upperBound) versus Random.default.next(upperBound: upperBound) to be nearly the same.

RandomTests
import XCTest

class RandomTests: XCTestCase {

  private let _upperBounds: ClosedRange<UInt64> = 1 ... 1_000_000

  func test_UInt64_random() {
    measure {
      for upperBound in _upperBounds {
        _ = UInt64.random(in: 0 ..< upperBound)
      }
    }
  }

  func test_Random_default_next() {
    measure {
      for upperBound in _upperBounds {
        _ = Random.default.next(upperBound: upperBound)
      }
    }
  }
}

I used the latest Trunk Development (master) snapshot, because apple/swift#16501 isn't in the Swift 4.2 branch yet.

1 Like

At the risk of continuing the bikeshedding, we might consider calling it StandardRandomNumberGenerator… it is the RNG provided by the standard library, after all. This eliminates the repetitiveness of the word default and implies that there are other (non-standard) RNGs that can be used.

2 Likes

I'd be happy with StandardRandomNumberGenerator, DefaultRandomNumberGenerator is closer to existing standard library types (like DefaultIndices) but it's much of a muchness. The only thing I would say (nitpicking) is that I suspect the vast, vast, majority of Swift projects will never need anything but the default generator. Apps/websites etc regularly use randomness to select objects, but customising the underlying algorithm is a rare requirement.

If you are working on a project that does require customisation in this department, you would surely be aware of the RandomNumberGenerator protocol and its inherent extensibility. Basically, I'm saying the implied minor additional meaning of a 'standard' prefix probably doesn't add anything.

1 Like

This kind of reasoning is backwards: you’re letting the tail wag the dog.

Consider what would happen when someone creates their own RNG—which is, after all, the whole point of exposing the protocol as we did. If that custom RNG has a long name, then it’s unwieldy to use under any circumstance. If it has a short name, then as you point out it’s going to be prone to misuse.

What you propose in this amendment would at best address this issue for the default RNG, and at the cost of an inelegant name, but the underlying problem you identify is with the design of the protocol, which goes unaddressed.

If this is truly the motivating issue for this amendment, then the most appropriate amendment that can be proposed is redesigning the protocol itself. It was mentioned earlier that a _fill(bytes:) method is currently underscored and waiting to be proposed. To prevent misuse, there is no need for the generic numeric next() and next(upperBound:) extension methods—they can be removed in favor of _fill(bytes:) and the implementations to obtain a number within a range from random bits can be moved into T.random(in:). This removal would address the issue you present in a comprehensive way rather than using unwieldy naming as a crutch here.

10 Likes

Just to add some nuance here: I for one, am a regular app developer, but I'm still the opposite of what you describe, since I'll probably never use the default generator, because most scenarios in which I currently use randomness (graphics/audio/signal processing or procedural generation of levels etc) requires seedable/repeatable sequences of random data, so my only random needs is fast and reasonably high quality PRNGs, which have to implement myself, but I'd still think it would be nice if I could implement them as conforming to RandomNumberGenerator, and perhaps a future PseudoRandomNumberGenerator rather than continue to use my own separate random api.

I'm not sure that I'm alone in this, for example: How many game developers need a non-seedable cryptographically secure RNG rather than a PRNG that is probably around 100 times faster (and seedable)? (rethorical question, not intended to derail conversation).

3 Likes

Fair, I was probably projecting my own ~8 years of app development on the world (it would be nice to have hard data) too much there.

I still think though that if your project is in the domain of randomness, you would find the RandomNumberGenerator protocol (in autocomplete, or perhaps by seeing struct DefaultRandomNumberGenerator : RandomNumberGenerator { in the generated stdlib source) and the name of the default generator is probably not the primary signpost.