I am currently trying to implement a function which gives me a random duration between zero and an upper limit from a generic Clock.
Since Clock.Duration is only constrained to DurationProtocol, I don't think there is a possibility to implement this, other than using Swift.Duration as a requirement. Fortunately Swift.Duration exposes seconds and attoseconds as raw integers by accessing components. These are two Int64s so to get a correct random number from a range, I'd have to combine those two to a Int128. Fortunately, again, this was added in Swift 6.
This was the final product, of what I came up with:
The Duration associated type is constrained to conform to DurationProtocol which refines AdditiveArithmetic. That doesn't even have to be integers, though, so I don't know if there's a straightforward way to do something generic over all Clock types due to this. Maybe you could constrain to just SignedNumeric and figure out how to generate random values of that?
You don't need either an 128-bit integer or floating-point types:
For a uniform random value, generate a random seconds component between 0...maxDuration.components.seconds and a random attoseconds component between 0..<1_000_000_000_000_000_000. Reject the random value and generate another one until (seconds, attoseconds) < maxDuration.components.
That's also interesting. Comes with the downside, that lower max durations (especially below 1 second) have higher risks of running (almost) indefinitely, right?
extension Clock where Duration == Swift.Duration {
func randomDuration(upTo maxDuration: Duration) -> Duration {
var randomDuration: Duration
repeat {
let randomSeconds = Int64.random(in: 0...maxDuration.components.seconds)
let randomAttoseconds = Int64.random(in: 0..<1_000_000_000_000_000_000)
randomDuration = .init(secondsComponent: randomSeconds, attosecondsComponent: randomAttoseconds)
} while randomDuration >= maxDuration
}
}
In practice it's quite efficient (for the specific bound in question, even using fairly naive rejection sampling, only one sample is needed 97.5% of the time, and two samples suffice 99.95% of the time. So it's "non-uniform" but in a very uninteresting way where it's "always" fast enough not to matter (i.e. it's "possibly" very long, but it's much more likely that a massive asteroid hits the earth while your program is running, which makes the possibility pretty uninteresting.)
For attosecond max durations performance can be pathological—if that's a case you need to handle, anything under 9 seconds is less than Int64.max attoseconds, so you could branch on that condition and use 64-bit arithmetic in the obvious manner to handle it.
I just looked at the source code of Duration and Int128 and both of them have underscored low and high (U)Int64s properties with underscored initializers. This would probably be the most succinct option. A bummer that it is underscored.
extension Clock where Duration == Swift.Duration {
func randomDuration(upTo maxDuration: Duration) -> Duration {
let random = Int128.random(in: 0..<Int128(_low: maxDuration._low, _high: maxDuration._high))
return .init(_high: random._high, low: random._low)
}
}
What you're getting at is that Duration's API hasn't been updated since the (U)Int128 has been added to the language. (There are also a handful of other spots among the Duration APIs which some folks have been meaning to get to.)
It would make sense that the ultimate final shape of the APIs here allow you to retrieve and set a duration in 128-bit attoseconds directly.
Would exposing this functionality be worth pitching? The implementation would probably just be exposing the existing code publicly and guarding for Stdlib 6.0 in order to get non-underscored Int128.