Calling Swift 4 generic method produce error - swift

Given the following method to a Dictionary like class (Lets say the Table class) where the key and value are both data type that conforms to the protocol Value. Number types, String, Bool, and etc are extended to conform to the Value protocol.
open class Table: StoredValue {
...
open func asDictionary<K1: Value, V1: Value, K2: Value, V2: Value>(
_ kfn: (K1) -> K2 = {$0 as! K2},
_ vfn: (V1) -> V2 = {$0 as! V2}
) -> [K2: V2] where K2: Hashable {
var v = [K2: V2]()
for key in keys() {
let val = self[key]
if key is K1 && val is V1 {
v[kfn(key as! K1)] = vfn(val as! V1)
}
}
return v
}
}
The Table class itself is not a generic class but the methods for converting to native Swift container classes are generic.
The issue is calling the method from my code. I have tried to use various way to call the method, I just can't get it to work. Googling did not find any solutions either. I believe it works prior to Swift 4.x but something has change.
For example:
Calling the method with the default conversation closure as such:
let dict = options.asDictionary()
Xcode will complain (rightly so) with the error "Generic parameter 'K1' could not be inferred"
Calling the method with the target type as such:
let dict: [String: Any] = options.asDictionary()
Xcode will complain with the error "Cannot convert value of type '[_ : _]' to specified type '[String : Any]'"
Supplying the conversion closure like this:
let dict: [String: Any] = options.asDictionary({ $0 as! String }, { $0 as Any })
Xcode complains with the error "Expression type 'String' is ambiguous without more context"
Is the generic method definition wrong?

Related

How can make a custom extension for Dictionary in Swift?

I am trying to add a function to Dictionary, this func called add, which has 2 input values (key and value). I tried to make the func be generic, that means I want key and value be able take any Type. I end it up to this down non-working code. What I should do next to make this func work?
extension Dictionary {
func add<Key, Value>(key: Key, value: Value) {
self[Key] = Value
}
}
First, you are trying to assign a type instead of an instance to the dictionary.
Type of expression is ambiguous without more context):
Second, you need to declare your method as mutating.
Cannot assign through subscript: subscript is get-only).
Third, you are creating two new generic types that have no relation with the Dictionary generic Key and Value types.
Cannot convert value of type 'Key' (generic parameter of instance method 'add(key:value:)') to expected argument type 'Key' (generic parameter of generic struct 'Dictionary').
extension Dictionary {
mutating func add(key: Key, value: Value) {
self[key] = value
}
}
var dict: [String: Int] = [:]
dict.add(key: "One", value: 1)
dict // ["One": 1]

iOS 12 SDK Generic function returns Optional.some(nil)

Using Xcode 10, but did not migrate to Swift 4.2, so my project is still running with Swift 4.1.
Lets assume i have the following extension on Dictionary:
extension Dictionary where Key: ExpressibleByStringLiteral {
func find<T>(key: Key) -> T? {
return self[key] as? T
}
}
I use this function to access values in a hashmap in a type safe manner like:
let dict: [String: Any] = ["foo": "bar"]
let foo: String? = dict.find(key: "foo") // prints "bar"
My problem surfaces, when i would like to have Any type returned from my find function, like:
let bar: Any? = dict.find(key: "bar")
Pre Xcode 10, this function used to return to me plain and simple nil if the key was not found in the hashmap.
However, post Xcode 10, it returns Optional.some(nil).
I understand, that Any types can be initialised like the following:
let foo: Any = Optional<String>.none
I guess in my case something similar happens. Does anybody has an idea, how to work around it and still return nil from the find function?
This is due to an intentional change (#13910) where the compiler is now more conservative with unwrapping an optional value that's being cast to a generic placeholder type. Now, the results you get are more consistent with those that you would get in a non-generic context (see SR-8704 for further discussion).
For example:
// note the constraint `where Key : ExpressibleByStringLiteral` is needlessly restrictive.
extension Dictionary where Key : ExpressibleByStringLiteral {
func find<T>(key: Key) -> T? {
return self[key] as? T
}
}
let dict: [String: Any] = ["foo": "bar"]
let genericBar: Any? = dict.find(key: "bar")
print(genericBar as Any) // in Swift 4.1: nil, in Swift 4.2: Optional(nil)
// `T` in the above example is inferred to be `Any`.
// Let's therefore substitute `as? T` with `as? Any`.
let nonGenericBar = dict["bar"] as? Any
print(nonGenericBar as Any) // in both versions: Optional(nil)
As you can see, you now get Optional(nil) regardless of whether a generic placeholder was used in order to perform the cast, making the behaviour more consistent.
The reason why you end up with Optional(nil) is because you're performing a conditionally casting an optional value. The conditional cast on its own results in an optional value in order to indicate success or failure, and putting the resulting optional value in the success case gives you a doubly wrapped optional. And because you're casting to Any, which can represent an Optional value, no unwrapping needs to be done in order to "fit" the value in the resultant type.
If you wish to flatten the resulting optional into a singly wrapped optional, you can either coalesce nil:
extension Dictionary {
func find<T>(key: Key) -> T? {
return (self[key] as? T?) ?? nil
}
}
Or unwrap the value before casting, for example using a guard:
extension Dictionary {
func find<T>(key: Key) -> T? {
guard let value = self[key] else { return nil }
return value as? T
}
}
or, my preferred approach, using flatMap(_:):
extension Dictionary {
func find<T>(key: Key) -> T? {
return self[key].flatMap { $0 as? T }
}
}
That all being said, I often find the usage of [String: Any] to be a code smell, strongly indicating that a stronger type should be used instead. Unless the keys can really be arbitrary strings (rather than a fixed set of statically known keys), and the values can really be of any type for a particular key – there's almost certainly a better type you can be using to model your data with.

Generic Functions and Protocols in Swift 3.2 failing to infer type

Given the following .
func doSomething<T: JSONDecodable>() -> [T] { return [] }
and a concrete type of Animal: JSONDecodable I'm trying to invoke the doSomething method without specifying the type of Animal.
The following works: let result: [Animal] = doSomething()
The following examples do not:
let result: [JSONDecodable] = doSomething() // Cannot convert value of type '_' to closure result 'JSONDecodables'
Storing out the type:
let savedType = Animal.Type
let result: [savedType] = doSomething() // Use of undeclared type savedType
Any suggestions to how to hint the compiler without specifying the concrete type?

Why are where clauses only valid on functions with generic parameters?

It seems absurd that this method signature does not compile in Swift 4:
class Bar<ValueType> {
func version() throws -> String where ValueType == [String: Any] { ... }
}
(Error: where clause cannot be attached to a non-generic declaration)
but this compiles fine:
class Bar<ValueType> {
func version<T>(_ foo: T? = nil) throws -> String where ValueType == [String: Any] { ... }
}
Anyone have insight as to why this is the case?
Because ValueType has nothing to do with this method (in the first example). It would be wrong to put such a method in a type (class/struct/enum), since it's not really a true member of that type. It's conditionally a member of that type, depending on the truth value of the where clause.
To achieve this, you would want to put this method in an extension of your type, with the where clause you want. E.g.
extension YourType where ValueType == [String: Any] {
func version() throws -> String { ... }
}
That has been finally allowed in Swift 5.3. See Contextual Where Clauses in the official Language Guide.
Citing from there:
You can write a generic where clause as part of a declaration that doesn’t have its own generic type constraints, when you’re already working in the context of generic types. For example, you can write a generic where clause on a subscript of a generic type or on a method in an extension to a generic type.
...
extension Container {
func average() -> Double where Item == Int {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
That compiles just fine in Xcode 12 beta 4, but won't work in Xcode 11.6 that is shipped with Swift 5.2.4.

Swift Cast to Generic Type with Constraint

I am using Swift 3 with constrained generics (i.e. a where clause). I have a problem when I am trying to do generic type casting. Here is a simplified example of the problem:
func jsonToObj<T:DomainResource>(jsonStr: String) -> [T:DomainResource] {
let json = JSON(parseJSON: jsonStr).dictionaryObject
let bundle = SMART.Bundle(json: json)
let result = bundle.entry?.map() {
return $0.resource as! T
}
return result!
}
My problem is when I return from the method, the compiler complains its cannot convert type [T] to type [T:DomainResource]. If I remove the DomainResource constraint from the generic, it compiles and runs just fine.
That's not what I want, so, I tried this:
let result = bundle.entry?.map() {
return $0.resource as! T:DomainResource
}
Swift doesn't seem to know what that means. Any idea on how to work around this problem? I'd like to not just cast them all to DomainResource objects, if possible.
You wrote this function signature:
func jsonToObj<T:DomainResource>(jsonStr: String) -> [T:DomainResource]
This says that the jsonToObj(jsonStr:) method returns a dictionary whose keys are of type T and whose values are of type DomainResource. It looks like you just want to write this function signature:
func jsonToObj<T:DomainResource>(jsonStr: String) -> [T]