Using ranges in a dictionary

I have this line of code working well:

let rangeName = [0..<1.3: "Very low", 1.3..<3: "Average", 3.0..<100: "Excellent"]

But if I change it to (see last range):

let rangeName = [0..<1.3: "Very low", 1.3..<3: "Average", 3.0...100: "Excellent"]

I have this error:

error: heterogeneous collection literal could only be inferred to '[AnyHashable : String]'; add explicit type annotation if this is intentional
let rangeName = [0..<1.3: "Very low", 1.3..<3: "Average", 3.0...100: "Excellent"]
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                                                                  as [AnyHashable : String]

I don't know how to specify the type to make it compile. Is it even possible?

There is RangeExpression, but that doesn‘t require Hashable And it has an associated type, so it won‘t help much here.

I‘m not sure a dictionary is the appropriate structure here anyway.

If you want to get a string for a concrete score (or whatever your number represents), a function containing a switch would not exhibit this same issue, and would even allow more flexibility (want a case 100: return "perfect"? That would be hard to do with a dict).

If you want to categorize your score first, and then get a description later, I‘d suggest converting your score into an enum value first, and then have a property on the enum for the description (or have a dict mapping from a “score category” enum to description).

I was using a switch in the previous version. My goal is more about playing with the code than having the best solution.

I don't understand the solution about using an enum. Each enum case would need the same value and not different values of type String.

Is it possible to create a variable to type RangeExpression if this type has an associated type? I don't know how if this is the case.

It is not, currently. You could write a type-erased wrapper, but that still would leave the complication of RangeExpression not requiring Hashable.

Wrt. the enum approach, I was thinking of something like this:

enum ScoreCategory {
    case low, average, good
    
    init?(score: Double) {
        switch score {
            case 0..<1.3: self = .low
            case 1.3..<3: self = .average
            case 3...100: self = .good
            case _: return nil
        }
    }
}

let descriptions: [ScoreCategory: String] = [
    .low: "Very low",
    .average: "Average",
    .good: "Excellent",
]

OK, thank for the solution.

But I don't understand how can protocols with associated types can be truly useful if we cannot use them as an interface. I mean we cannot put them in a container like an array for example and use the protocol as an interface to communicate with the members of the array. So what is the purpose? We use them to apply restrictions, but we cannot use them like protocols having no associated types.

I see that I can create the array that I talked about using a generic:

protocol S {
    associatedtype X
}
protocol T: S where X == Double {}

class C<XX: T> {
    var x: [XX]

    init(_ x: [XX]) {
        self.x = x
    }
}

class D: T {
}

But I cannot create a variable of type T. I find this strange.

This is the primary thread discussing the reasons for that:

https://forums.swift.org/t/lifting-the-self-or-associated-type-constraint-on-existentials/18025

Great! I have difficulties with the vocabulary used. Is a glossary available?

I played with protocols with associated types (PAT). And my conclusion is that we have to use generics to use them like regular protocols. We cannot use protocol composition either.

Here is an example:

protocol S {
    associatedtype X
    func f(_ x: X)
}

protocol T: S where X == Double {}

protocol U {}

class C<XX: T> {
    var x: [XX]

    init(_ x: [XX]) {
        self.x = x
    }
}

class D: T, U {
    func f(_ x: Double) {
        print(x)
    }
}

func x<E: T>(_ v: E) where E: U {
    v.f(13.0)
}

D().f(10)
C([D()]).x[0].f(1.0)
x(D())

In function x we cannot use protocol composition so the clause where E: U is used as an equivalent constraint.

I was expecting to be able to use protocol T like a regular protocol because its associated type X is bound to a concrete type so there is no type ambiguity. I hope this constraint will be left soon. :grinning: