[swift-users] How to be DRY on ranges and closed ranges?

Dave Abrahams dabrahams at apple.com
Sun Oct 16 17:25:28 CDT 2016


on Wed Oct 12 2016, Jean-Denis Muys <swift-users-AT-swift.org> wrote:

> Hi,
>
> I defined this:
>
> func random(from r: Range<Int>) -> Int {
>     let from = r.lowerBound
>     let to =  r.upperBound
>
>     let rnd = arc4random_uniform(UInt32(to-from))
>     return from + Int(rnd)
> }
>
> so that I can do:
>
> let testRandomValue = random(from: 4..<8)
>
> But this will not let me do:
>
> let otherTestRandomValue = random(from: 4...10)
>
> The error message is a bit cryptic:
>
> “No ‘…’ candidate produce the expected contextual result type ‘Range<Int>’”
>
> What is happening is that 4…10 is not a Range, but a ClosedRange.
>
> Of course I can overload my function above to add a version that takes a ClosedRange.
>
> But this is not very DRY.
>
> What would be a more idiomatic way?

In our original design we had a common protocol to which open and closed
ranges conformed, and you could pass an instance of RangeProtocol.  That
was removed because the overall complexity was not a win, but I predict
it will come back for Swift 4:
https://github.com/apple/swift/pull/3737

But for what you want to do, you don't need anything that complicated:

protocol CompleteRange {
  associatedtype Bound : Comparable
  var lowerBound : Bound { get }
  var upperBound : Bound { get }
}

extension CountableRange : CompleteRange {}
extension CountableClosedRange : CompleteRange {}

import Darwin
func random<R: CompleteRange>(from r: R) -> Int where R.Bound == Int, R: Collection {
  let rnd = arc4random_uniform(numericCast(r.count))
  return r.lowerBound + Int(rnd)
}

print(random(from: -5..<5), random(from: -20...20))

Hope this helps,

-- 
-Dave



More information about the swift-users mailing list