jonesjones
jonesjones

Reputation: 497

How to extend a protocol adding a computed static var

I'd like to extend the CaseIterable protocol so that all CaseIterable enums have a random static var that returns a random case. This is the code I've tried

public extension CaseIterable {
    static var random<T: CaseIterable>: T {
        let allCases = self.allCases
        return allCases[Int.random(n: allCases.count)]
    }
}

But this fails to compile. Is there a way to achieve this using a static var? Or if not, how would I write the equivalent static func?

ps Int.random extension for anyone playing along at home:

public extension Int {
    static func random(n: Int) -> Int {
        return Int(arc4random_uniform(UInt32(n)))
    }
}

Upvotes: 2

Views: 163

Answers (3)

Rob Napier
Rob Napier

Reputation: 299265

The existing answers are correct about how to fix it, but the why is probably as useful to understand. This is not doing what you think:

public extension CaseIterable {
    static var random<T: CaseIterable>: T {
        // ...
    }
}

This defines a property, random, which will return a value of type T, which is any type selected by the caller that conforms to CaseIterable. For example, according to this definition, I can make the following call:

let direction: LayoutDirection = Axis.random

And in this case, Axis promises that it will return a LayoutDirection, because that's what the caller has passed as T. Clearly this isn't what you want. You mean, every CaseIterable can return a random value of its own type, and as the other answer show, you do that with static var random: Self.

Generic type parameters (like T in this case) are parameters. They're passed by the caller, not chosen by the implementer.

Upvotes: 2

Leo Dabus
Leo Dabus

Reputation: 236260

You can NOT create a generic type on a computed property. There is no need to randomize an index. You can use Collection's randomElement method to return a random case and return Self:

public extension CaseIterable {
    static var random: Self { allCases.randomElement()! }
}

Playground testing:

enum Test: String, CaseIterable {
    case a, b, c, d, e
}

let test: Test = .random  // d

If you would like to return a random value you can constrain Self to RawRepresentable and return RawValue:

public extension CaseIterable where Self: RawRepresentable {
    static var randomValue: RawValue { allCases.randomElement()!.rawValue }
}

enum Test: String, CaseIterable {
    case a, b, c, d, e
}

let test = Test.randomValue   // "c"

Upvotes: 2

pawello2222
pawello2222

Reputation: 54426

You can actually use randomElement directly in the CaseIterable extension:

public extension CaseIterable {
    static var random: Self {
        allCases.randomElement()!
    }
}

Upvotes: 2

Related Questions