Home > Software engineering >  Swift: The Any type and optionals
Swift: The Any type and optionals

Time:10-04

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.

  • Related