This thread is meant as a more general discussion of how the Random API could be improved, following up on threads like this (about a an issue that arises from the current API design) and this (about improving the methods for generating uniform floating point numbers as well as unsigned integers below a given upper bound).
As an initial suggestion I'd like to propose a simplification of the API that would solve the problem described in the first thread and make it at least a little more straight forward to write the improvements mentioned in the second.
I suggest we keep the core of the RandomNumberGenerator
protocol as it is today, ie:
public protocol RandomNumberGenerator {
/// Returns a value from a uniform, independent distribution of binary data.
///
/// Use this method when you need random binary data to generate another
/// value. If you need an integer value within a specific range, use the
/// static `random(in:using:)` method on that integer type instead of this
/// method.
///
/// - Returns: An unsigned 64-bit random value.
mutating func next() -> UInt64
}
But that (over time) we abandon
next() -> T
,
next(upperBound:) -> T
and
T._random()
as they can all be replaced by this single method:
extension RandomNumberGenerator {
/// Returns a random value that is less than or equal to the given value.
///
/// Use this method when you need random binary data to generate another
/// value. If you need an integer value within a specific range, use the
/// static `random(in:using:)` method on that integer type instead of this
/// method.
///
/// - Parameter maxValue: The greatest value that will be generated.
/// - Returns: A random value of `T` in the range `0...maxValue`. Every
/// value in the range `0...maxValue` is equally likely to be returned.
@inlinable
public mutating func next<T: FixedWidthInteger & UnsignedInteger>(
lessThanOrEqualTo maxValue: T = T.max
) -> T {
// NOTE: Any value is a valid `maxValue` so no preconditions needed.
if maxValue == 0 { return 0 }
if T.bitWidth <= UInt64.bitWidth {
if maxValue == T.max {
return T(truncatingIfNeeded: self.next())
// Or, we might implement some buffering here in order to not
// throw away eg 56 bits for each call when T is UInt8.
}
// Code for the case when T.bitWidth <= 64 and maxValue < T.max,
// ideally it would use the random bits as efficiently as possible
// (ie only one bit per call is consumed if maxValue == 1 no
// matter the type of T.
// ...
}
// Code for the case when T.bitWidth > 64.
// ...
}
}
The buffering thing is only mentioned as an idea, and is of secondary concern, as it would require stored properties ( bufferedBits: UInt64
and bufferedBitCount
) either as requirements of the protocol or via an additional type
( BufferedRandomNumberGenerator<Base: RandomNumberGenerator> : RandomNumberGenerator
{ … } )
... and I guess it might not be worth its weight.
What do you think, would it make sense to introduce this method (sans the buffering) and phase out the ones it replaces?