> On Sep 8, 2017, at 2:46 PM, Jacob Williams via swift-evolution 
> <[email protected]> wrote:
> 
> What if we did it with something like this:
> 
> protocol RandomGenerator {
>       associated type T: Numeric // Since numeric types are the only kinds 
> where we could get a random number?
>       func uniform() -> T
>       // Other random type functions...
> }
> 
> Although if we didn’t constrain T to Numeric then collections could also 
> conform to it, although I’m not sure that collections would want to directly 
> conform to this. There may need to be a separate protocol for types with 
> Numeric indexes?
> 
> I’m no pro and really haven’t thought about this too deeply. Mostly just 
> spitballing/brainstorming.

I think I would simply say:

        /// Conforming types generate an infinite sequence of random bits 
through their `next()` method.
        /// They may be generated from a repeatable seed or from a source of 
true entropy.
        protocol Randomizer: class, IteratorProtocol, Sequence where Element == 
UInt, Iterator == Self {
                /// Generates and returns the next `UInt.bitWidth` bits of 
random data.
                func next() -> UInt
        }

And have this extension on it:

        extension Randomizer {
                /// Permits the use of a Randomizer as a plain old iterator.
                func next() -> UInt? {
                        return Optional.some(next())
                }
                
                /// Returns a number in the range 0 ... maximum.
                /// (This is inclusive to allow `maximum` to be `UInt.max`.)
                func next(through maximum: UInt) -> UInt {
                        …
                }
        }

We should also provide a singleton `StrongRandomizer`:

        /// A source of cryptographically secure random data.
        /// 
        /// `StrongRandomizer` typically uses the strongest random data source 
provided by  
        /// the platform that is suitable for relatively frequent use. It may 
use a hardware RNG 
        /// directly, or it may use a PRNG seeded by a good entropy source.
        /// 
        /// `StrongRandomizer` is inherently a singleton.
        /// It can be used on multiple threads, and on some platforms it may 
block while its 
        /// shared state is locked.
        class StrongRandomizer: Randomizer {
                static let shared = StrongRandomizer()
                
                func next() -> UInt {
                        …
                }
        }

Finally, we can add extensions to `RandomAccessCollection`:

        extension RandomAccessCollection {
                func randomElement(with randomizer: Randomizer = 
StrongRandomizer.shared) -> Element? {
                        guard !isEmpty else { return nil }
                        let offset = IndexDistance(randomizer.next(through: 
UInt(count) - 1))
                        return self[index(startIndex, offsetBy: offset)]
                }
        }

And ranges over `BinaryFloatingPoint`:

        extension Range where Bound: BinaryFloatingPoint {
                func randomElement(with randomizer: Randomizer = 
StrongRandomizer.shared) -> Bound? {
                        …
                }
        }
        extension ClosedRange where Bound: BinaryFloatingPoint {
                func randomElement(with randomizer: Randomizer = 
StrongRandomizer.shared) -> Bound {
                        …
                }
        }

A couple of notes:

• Fundamentally, a randomizer is an infinite sequence of random-ish bits. I say 
"random-ish" because you may be generating a repeatable pseudo-random sequence 
from a seed. But in any case, I think it's best to envision this as a special 
case of an iterator—hence the `IteratorProtocol` conformance.

• Randomizer is class-constrained because we want to encourage providing a 
defaulted parameter for the randomizer, and `inout` parameters can't be 
defaulted.

• `UInt` is intentionally inconvenient. You should not usually use a randomizer 
directly—you should pass it to one of our methods, which know how to handle it 
correctly. Making it awkward discourages direct use of the randomizer.

• `next(through:)` is provided simply to discourage incorrect modulo-ing.

• `StrongRandomizer` is provided as a slow but safe default. I don't think we 
should ship any other RNGs; people who want them can import them from modules, 
which provides at least a bit of evidence that they know what they're doing. 
(Actually, I could see adding a second RNG that's even stronger, and is 
specifically intended to be used infrequently to generate seeds or important 
cryptographic keys.)

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to