FixedWidthInteger.random()

I found myself often reaching out for:

Int16.random(in: .min ... .max) // or another integer type
.random(in: .min ... .max)      // when type is inferred

After creating this helper:

extension FixedWidthInteger {
    public static func random<T>(using generator: inout T) -> Self where T : RandomNumberGenerator {
        random(in: .min ... .max, using: &generator)
    }
    public static func random() -> Self {
        random(in: .min ... .max)
    }
}

I can now use a simpler:

Int16.random()
.random()        // when type is inferred

Should we have such helper in the standard library or is it too niche / not enough win?

1 Like

This was specifically excluded in SE-0202, in part due to risk of misuse: we want people to choose the range they want rather than inadvertently using %. You can refer to the (extensive) discussions archived here for more info.

A slight compromise here, if this isn't already available, would be versions that accept partial and unbounded ranges.

Int16.random(in: ... , using: &rng)

Is less verbose without enouraging modulo bias.

4 Likes

Alternatively:

let x = Int.random(in: .all)

where:

extension ClosedRange where Bound: FixedWidthInteger {
    static var all: Self { // `fullRange` ?
        .min ... .max
    }
}

which could be used not just with random but anywhere .min ... .max is needed.


.min ... .max fragment is somewhat on the heavy side syntax wise, hard to comprehend at a glance, not auto-completed and could be mistypes as:

.max ... .max
.max ... .min
.min ..< .max
0 ... .max
1 Like

If the fixed-width integer type is unsigned, then you can call rng.next() as T.

Is there a principled reason for not allowing this with signed types as well?

See the following previous thread:


The general solution for filling any buffer with random bytes is mentioned in the thread linked above and has been pitched; it is to be re-pitched Any Day Now(TM).

The spelling for an unbounded range is ..., so I don't see why it "should have" another spelling. It is absolutely intended that ... should be supported, and that it is not is an implementation shortcoming previously discussed in this thread.

2 Likes

Thank you, this:

Int.random(in: ...)

I can live with, it's almost obvious.

Perhaps also:

Double.random(in: .unitRange)

as a shorthand to:

Double.random(in: 0..<1) // or should it be 0...1 ?

Edit: Having put these two options next to each other:

Int.random(in: ...)
Double.random(in: .unitRange)
Int.random(in: .fullRange)
Double.random(in: .unitRange)

The first option starts looking as an overly clever trick

Edit: similarly we can have these convenient ranges:

Int.random(in: .naturalNumbers)
Int.random(in: .positiveNumbers)
Int.random(in: .negativeNumbers)

This is pretty clearly a mis-feature and a mistake in the original design, rather than a good precedent to follow for new API.

Fundamentally the problem is that this confuses random sources and transformed random values. RandomNumberGenerator should have properly been called RandomSource or RandomBitStream or something similar; it doesn't generate numbers, it generates bits that can be transformed to generate random numbers, among other things (the fact that it happens to pack those bits into a UInt64 container is incidental). I'm skeptical that we can fix this, but it's mostly only a minor annoyance. But we should definitely avoid introducing more confusion between the two concepts.

8 Likes

I disagree that the unbounded range is appropriate here. The range of numbers that can be generated by the method is not unbounded, it is (.min)...(.max). Also, if we apply this to the other range operators, then it could be more confusing. It would be easy to accidentally read something like Int.random(in: ...5) as Int.random(in: 0...5) instead of what it actually would mean. I think fullRange is a better term since it more clearly demonstrates the limits on the numbers that will be produced by the function. (Though Range.fullRange is a bit redundant — perhaps there's a more mathematical word that could be used instead?)

2 Likes

That is a fair point: indeed, this is essentially the argument why random() was excluded as an API.

I don't agree that fullRange is a better term: indeed, I think having a second spelling raises the question of what "full" means—since I can write Int.min...Int.max easily and ... even more easily (which in such contexts always denotes the complete range of representable values), does "full" mean something fuller that can't be expressed otherwise?—and what "range" means—in Swift, when there is no adjective "closed," a "range" is an "open range," so is this Int.min..<Int.max?

If the fair point you make is the paramount concern, then one could not have a solution that "more clearly demonstrates" the bounds than the status quo where every bound needs to be explicitly written out. This is in fact the spelling you've naturally used above to explain "what it actually would mean" versus what it can be confused with.

2 Likes