This is something that has vexed a number of developers including myself. Let say we have a protocol that defines a subscript which we apply to a simple class.
protocol Cache {
subscript<Value>(_: String) -> Value? { get set }
}
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript<Value>(key: String) -> Value? {
get {
cache[key] as? Value
}
set {
if let value = newValue {
cache[key] = value
} else {
cache.remove(key)
}
}
}
}
This works fine as long as we know the types:
cache["abc"] = 5
let x: Int? = cache["abc"]
but the developers want to do this:
cache["abc"] = nil
Which won't compile because the compiler cannot determine the Value
generic type. This works however
cache["abc"] = nil as String?
I've tried a number of things but they all have drawbacks. Things like adding a second subscript with the Any
type. Nothing seems to work well even though it would seem like a simple problem.
So the question has anyone found a solution that handles cache["abc"] = nil
?
CodePudding user response:
You can do this by changing your protocol requirements somewhat.
Have the protocol require a subscript that does not use generics, and returns an Any?
.
protocol Cache {
subscript(key: String) -> Any? { get set }
}
This subscript will let you do the following:
cache["abc"] = 5
cache["abc"] = nil
let value = cache["abc"] // value is an `Any?`
but it will not let you do this:
let number: Int? = cache["abc"] // error
So, let's fix that by adding another subscript to Cache
. This subscript is equivalent to your original subscript requirement, except it doesn't need a setter and will call the other subscript (the one required by the protocol):
extension Cache {
subscript<Value>(key: String) -> Value? {
self[key] as? Value
}
}
Then, implement the required subscript in your class:
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript(key: String) -> Any? {
get { cache[key] }
set { cache[key] = newValue }
}
}
This will allow all of the following to compile:
let cache = InMemoryCache()
cache["abc"] = 5
let x: Int? = cache["abc"]
cache["abc"] = nil
CodePudding user response:
There is a workaround to have your desire output.
Because this is a dictionary so you get assign nil
directly in your InMemoryCache
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript<Value>(key: String) -> Value? {
get {
cache[key] as? Value
}
set {
if let value = newValue {
cache[key] = value
} else {
cache[key] = nil // make nil directly here
}
}
}
}
In here because of Value
is a generic type. So you can not assign nil
directly. It must have a specific type.
Instead you can do like this
let nilValue : Int? = nil // any type nil you want
cache["abc"] = nilValue
or directly cast it to nil of any tupe before assign to dictionary
cache["abc"] = (nil as String?)
It will refresh anything value is store in the key.
Example
// value
let nilValue : Int? = nil
var number : Int? = nil
var string : String? = nil
cache["abc"] = 5
number = cache["abc"] // Optional.some(5)
cache["abc"] = "abc"
number = cache["abc"] // nil
string = cache["abc"] // Optional.some("abc")
cache["abc"] = nilValue
number = cache["abc"] // nil
string = cache["abc"] // nil
CodePudding user response:
The reason why you are having a hard time with this is because
cache["abc"] = nil
cannot be compiled. There is not enough information to infer the generic type of the subscript - or of the optional value. The compiler sees something like
cache<?>["abc"] = Optional<?>.none
How is it supposed to figure out what to put in place of the question marks?
There's another ambiguity. Your cache can contain any type, even Optional
. When you are assigning nil
to the subscript, how does anybody know if you want to remove the element or store an instance of Optional<Something>.none
at the subscript?
When I find myself fighting the language in this way, I usually try to take a step back and ask if I am perhaps doing something fundamentally bad. I think, in this case, the answer is yes. You are trying to pretend something is more strictly typed than it really is.
I think your getter/setter should explicitly take a value that is of type Any
. It works better and it has the advantage that it explicitly documents for the user that a Cache
conforming type can store anything in it.
For this reason, I would say TylerP's solution is the best. However, I would not create a subscript in the extension, I would define a function
extension Cache
{
func value<Value>(at key: String) -> Value?
{
self[key] as? Value
}
}
The reason for this is that the compiler can get confused when you have multiple subscripts with similar signatures. With the extension above, I can conform Dictionary<String, Any>
to the protocol and not need a new class.
extension Dictionary: Cache where Key == String, Value == Any {}
var dict: [String : Any] = [:]
dict["abc"] = 5
let y: Int? = dict.value(at: "abc")
dict["abc"] = nil
Obviously, the above won't be useful to you if you need reference semantics for your cache.