How do I store a value of type Class<ClassImplementingProtocol> in a Dictionary of type [String:Class<Protocol>] in Swift? - swift

I want to store a more specialized type in a Dictionary of type [String:SomeClass]. Here is some sample code illustrating my problem (also available to play with at https://swiftlang.ng.bluemix.net/#/repl/579756cf9966ba6275fc794a):
class Thing<T> {}
protocol Flavor {}
class Vanilla: Flavor {}
var dict = [String:Thing<Flavor>]()
dict["foo"] = Thing<Vanilla>()
It produces the error ERROR at line 9, col 28: cannot assign value of type 'Thing<Vanilla>' to type 'Thing<Any>?'.
I've tried casting Thing<Vanilla>() as Thing<Flavor> but that produces the error cannot convert value of type 'Thing<Vanilla>' to type 'Thing<Flavor>' in coercion.
I've also tried to define the Dictionary as type [String:Thing<Any>] but that doesn't change anything either.
How do I create a collection of different Things without resorting to plain [String:AnyObject]?
I should also mention that the class Thing is not defined by me (in fact it's about BoltsSwift Tasks), so the solution to create a base class of Thing without a type parameter doesn't work.

A Thing<Vanilla> is not a Thing<Flavor>. Thing is not covariant. There is no way in Swift to express that Thing is covariant. There are good reasons for this. If what you were asking for were allowed without careful rules around it, I would be allowed to write the following code:
func addElement(array: inout [Any], object: Any) {
array.append(object)
}
var intArray: [Int] = [1]
addElement(array: &intArray, object: "Stuff")
Int is a subtype of Any, so if [Int] were a subtype of [Any], I could use this function to append strings to an int array. That breaks the type system. Don't do that.
Depending on your exact situation, there are two solutions. If it is a value type, then repackage it:
let thing = Thing<Vanilla>(value: Vanilla())
dict["foo"] = Thing(value: thing.value)
If it is a reference type, box it with a type eraser. For example:
// struct unless you have to make this a class to fit into the system,
// but then it may be a bit more complicated
struct AnyThing {
let _value: () -> Flavor
var value: Flavor { return _value() }
init<T: Flavor>(thing: Thing<T>) {
_value = { return thing.value }
}
}
var dict = [String:AnyThing]()
dict["foo"] = AnyThing(thing: Thing<Vanilla>(value: Vanilla()))
The specifics of the type eraser may be different depending on your underlying type.
BTW: The diagnostics around this have gotten pretty good. If you try to call my addElement above in Xcode 9, you get this:
Cannot pass immutable value as inout argument: implicit conversion from '[Int]' to '[Any]' requires a temporary
What this is telling you is that Swift is willing to pass [Int] where you ask for [Any] as a special-case for Arrays (though this special treatment isn't extended to other generic types). But it will only allow it by making a temporary (immutable) copy of the array. (This is another example where it can be hard to reason about Swift performance. In situations that look like "casting" in other languages, Swift might make a copy. Or it might not. It's hard to be certain.)

One way to solve this is adding an initialiser to Thing and creating a Thing<Flavor> that will hold a Vanilla object.
It will look something like:
class Thing<T> {
init(thing : T) {
}
}
protocol Flavor {}
class Vanilla: Flavor {}
var dict = [String:Thing<Flavor>]()
dict["foo"] = Thing<Flavor>(thing: Vanilla())

Related

Swift method needs to sort a polymorphic collection of UInt32

Confused (and a little frustrated) with Swift5 at the moment.
I have a method:
func oidsMany(_ oids:Array<UInt32>) -> MCCommandBuilder {
let sorted_oids:[UInt32] = oids.sorted()
...
}
Discovered I have a case where I want to pass a Set to this method just as well. Either way, I'm going to sort an Array or a Set to an Array right away.
Waded through the many many many protocols that both Set and Array conform to, noticed that they both conform to [Sequence][1] and that Sequence responds to sorted. Perfect.
But when I change the above to:
func oidsMany(_ Sequence<UInt32>) -> MCCommandBuilder {
let sorted_oids:[UInt32] = oids.sorted()
...
}
I get the following error hints:
Cannot specialize non-generic type 'Sequence'
Member 'sorted' cannot be used on value of protocol type 'Sequence'; use a generic constraint instead
What's the right way to approach this? I could just add a second oidsMany(_ Set...) that casts its arg as an array and recalls. But I feel like I'm missing something fundamental here. My experience from other languages is not mapping over well here.
You can as the error message suggest use it as a generic constraint instead
func oidsMany2<Sortable: Sequence>(_ oids: Sortable) -> MCCommandBuilder where Sortable.Element: Comparable {
let sorted_oids:[Sortable.Element] = oids.sorted()
//...
}
if you only want to accept collections where the element is UInt32 you can change the where condition to
where Sortable.Element == UInt32

Swift generic collection of Element cannot convert to collection of Any

I'm facing a problem I don't understand.
In my project, I want to make a collection of elements with some customised methods (like an update from a server). But when I try to group all these collections in an array, I get an error: "Cannot convert value of type MyCollection<someElement> to expected argument type MyCollection<Any>"
What I don't understand is that the same code with Array is working... Array isn't a collection?
// My collection which would contain an update method
class MyCollection<Element> {
var object:Element? = nil
}
let x = MyCollection<Int>()
var list = [MyCollection<Any>]()
list.append(x) //Cannot convert value of type 'MyCollection<In>' to expected argument type 'MyCollection<Any>'
let a = Array<Int>()
var lista = [Array<Any>]()
lista.append(a) //Doesn't get error at all...
I know I can do this with an array of the specific type but by grouping all of MyCollection in an array, I wish to use a code like :
func update() {
for e in list { // array of MyCollection<Any>
e.update()
}
}
Thank you in advance for your help ;)
Being able to convert from SomeType<Subtype> to SomeType<Supertype> is called covariance. In Swift, Array<T> is covariant on T by "compiler magic", and you can't do the same for your own types.
The type checker hardcodes conversions from Array to Array if there is a conversion from T to U. Similar rules exist for Optional and Dictionary. There's no mechanism for doing this with your own types.
Your own generic types are always invariant, meaning that there is never a conversion between SomeType<T> to SomeType<U>, as long as T and U are different types.
Let's imagine what would happen if the conversion on MyCollection were allowed. You could do:
let myCollectionInt = MyCollection<Int>()
let myCollectionAny: MyCollection<Any> = myCollectionInt // suppose you can do this
myCollectionAny.object = "string" // myCollectionAny.object is of type Any?, so this should be ok
We've set myCollectionAny.object to "string", but MyCollection is a reference type, so myCollectionInt.object should also be "string". But myCollectionInt.object is an Int?!
Of course this type-unsafety is also a problem with arrays, but the language designers have decided that casting arrays is a common enough thing to do, that disallowing it would do more hard than good.

Swift 5 storing and passing KeyPaths

Let's say I have the following class:
class User: NSObject {
var name = "Fred"
var age = 24
var email = "fred#freddy.com"
var married = false
}
I want to be able to write a generic function that takes in a list of KeyPaths for a known class type, read the values and print to screen. The problem is, the I can't get the following code to compile as the type of the KeyPath's Value is not known, and will be different for each time. What do I have to do to make this work generically?
Consider the following:
struct KeyPathProperties<T> {
var name: String
var relatedKeyPaths: [KeyPath<T, Any>]
}
extension KeyPath where Root == User {
var properties: KeyPathProperties<Root> {
switch self {
case \Root.name:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
default:
fatalError("Unknown key path")
}
}
}
This line fails to compile:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
with this error:
Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'
This is what I wish to be able to do, for instance:
let myUser = User()
var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])
for keyPath in props.relatedKeyPaths {
print("Value: \(myUser[keyPath: keyPath])")
}
The above won't compile of course. Essentially I want to store keyPaths in an array at runtime, so I can generically at some point in time get values out of the User. I need to know if I can re-write the above in some way where the compiler can safely and correctly determine the type of the keyPath's value at runtime.
This is a conceptual use case for a much more complex architectural issue I'm trying to solve with hopefully less code.
MORE INFORMATION:
At runtime I wish to keep track of the properties that get modified - these properties are held in a modifiedProps array in each object / instance. At some point at runtime, I wish to be able to enumerate over this array of KeyPaths and print their values like so:
for modifiedKeyPath in self.modifiedProps {
print ("\(self[keyPath: modifiedKeyPath])"
}
In short - I need to be able to capture the generic type of the KeyPath within KeyPathProperties. How do I achieve this?
SIDE NOTE: I can already easily achieve this by using Swift 3 style string based KeyPaths (by adding #objc to the class properties). I can store an array of keyPaths as strings and later do:
let someKeyPath = #keyPath(User.email)
...
myUser.value(forKeyPath: someKeyPath)
I just cannot do this with Swift 4 KeyPaths generically.
The error tells you what your misconception is:
Cannot convert value of type 'KeyPath<User, Int>'
to expected element type 'KeyPath<User, Any>'
You seem to think that you can use a KeyPath<User, Int> where a KeyPath<User, Any> is expected, ostensibly on the grounds that an Int is an Any. But that's not true. These are generic types, and generic types are not covariant — that is, there is no substitution principle for generics based on their parameterized types. The two types are effectively unrelated.
If you need an array of key paths regardless of their parameterized types, you would need an array of PartialKeyPath or AnyKeyPath. It seems that in your use case the root object is the same throughout, so presumably you want PartialKeyPath.

Can a condition be used to determine the type of a generic?

I will first explain what I'm trying to do and how I got to where I got stuck before getting to the question.
As a learning exercise for myself, I took some problems that I had already solved in Objective-C to see how I can solve them differently with Swift. The specific case that I got stuck on is a small piece that captures a value before and after it changes and interpolates between the two to create keyframes for an animation.
For this I had an object Capture with properties for the object, the key path and two id properties for the values before and after. Later, when interpolating the captured values I made sure that they could be interpolated by wrapping each of them in a Value class that used a class cluster to return an appropriate class depending on the type of value it wrapped, or nil for types that wasn't supported.
This works, and I am able to make it work in Swift as well following the same pattern, but it doesn't feel Swift like.
What worked
Instead of wrapping the captured values as a way of enabling interpolation, I created a Mixable protocol that the types could conform to and used a protocol extension for when the type supported the necessary basic arithmetic:
protocol SimpleArithmeticType {
func +(lhs: Self, right: Self) -> Self
func *(lhs: Self, amount: Double) -> Self
}
protocol Mixable {
func mix(with other: Self, by amount: Double) -> Self
}
extension Mixable where Self: SimpleArithmeticType {
func mix(with other: Self, by amount: Double) -> Self {
return self * (1.0 - amount) + other * amount
}
}
This part worked really well and enforced homogeneous mixing (that a type could only be mixed with its own type), which wasn't enforced in the Objective-C implementation.
Where I got stuck
The next logical step, and this is where I got stuck, seemed to be to make each Capture instance (now a struct) hold two variables of the same mixable type instead of two AnyObject. I also changed the initializer argument from being an object and a key path to being a closure that returns an object ()->T
struct Capture<T: Mixable> {
typealias Evaluation = () -> T
let eval: Evaluation
let before: T
var after: T {
return eval()
}
init(eval: Evaluation) {
self.eval = eval
self.before = eval()
}
}
This works when the type can be inferred, for example:
let captureInt = Capture {
return 3.0
}
// > Capture<Double>
but not with key value coding, which return AnyObject:\
let captureAnyObject = Capture {
return myObject.valueForKeyPath("opacity")!
}
error: cannot invoke initializer for type 'Capture' with an argument list of type '(() -> _)'
AnyObject does not conform to the Mixable protocol, so I can understand why this doesn't work. But I can check what type the object really is, and since I'm only covering a handful of mixable types, I though I could cover all the cases and return the correct type of Capture. Too see if this could even work I made an even simpler example
A simpler example
struct Foo<T> {
let x: T
init(eval: ()->T) {
x = eval()
}
}
which works when type inference is guaranteed:
let fooInt = Foo {
return 3
}
// > Foo<Int>
let fooDouble = Foo {
return 3.0
}
// > Foo<Double>
But not when the closure can return different types
let condition = true
let foo = Foo {
if condition {
return 3
} else {
return 3.0
}
}
error: cannot invoke initializer for type 'Foo' with an argument list of type '(() -> _)'
I'm not even able to define such a closure on its own.
let condition = true // as simple as it could be
let evaluation = {
if condition {
return 3
} else {
return 3.0
}
}
error: unable to infer closure type in the current context
My Question
Is this something that can be done at all? Can a condition be used to determine the type of a generic? Or is there another way to hold two variables of the same type, where the type was decided based on a condition?
Edit
What I really want is to:
capture the values before and after a change and save the pair (old + new) for later (a heterogeneous collection of homogeneous pairs).
go through all the collected values and get rid of the ones that can't be interpolated (unless this step could be integrated with the collection step)
interpolate each homogeneous pair individually (mixing old + new).
But it seems like this direction is a dead end when it comes to solving that problem. I'll have to take a couple of steps back and try a different approach (and probably ask a different question if I get stuck again).
As discussed on Twitter, the type must be known at compile time. Nevertheless, for the simple example at the end of the question you could just explicitly type
let evaluation: Foo<Double> = { ... }
and it would work.
So in the case of Capture and valueForKeyPath: IMHO you should cast (either safely or with a forced cast) the value to the Mixable type you expect the value to be and it should work fine. Afterall, I'm not sure valueForKeyPath: is supposed to return different types depending on a condition.
What is the exact case where you would like to return 2 totally different types (that can't be implicitly casted as in the simple case of Int and Double above) in the same evaluation closure?
in my full example I also have cases for CGPoint, CGSize, CGRect, CATransform3D
The limitations are just as you have stated, because of Swift's strict typing. All types must be definitely known at compile time, and each thing can be of only one type - even a generic (it is resolved by the way it is called at compile time). Thus, the only thing you can do is turn your type into into an umbrella type that is much more like Objective-C itself:
let condition = true
let evaluation = {
() -> NSObject in // *
if condition {
return 3
} else {
return NSValue(CGPoint:CGPointMake(0,1))
}
}

Can you enforce type conversion in a protocol without defining a property/method?

I don't want to have to define asString.
protocol ConvertibleToString {var asString: String {get}}
extension Int: ConvertibleToString {
var asString: String {return String(self)}
}
If I understand your question correctly – no, I don't think you can't define an "implicit" conversion that detects and uses a matching init from a specific type. The only way to convert from one type to another in Swift is to explicitly call an init for the "to" type that takes the "from" type, or a function or method on the "from" type that returns the "to" type. There's no way of implementing a protocol that says "use the init for this type with other type, if one is available".
By the way, your ConvertibleToString protocol is essentially a version of Printable (with asString in place of description). So if what you want is to know if something is convertible to a string, you can just check for conformance to Printable. Though note one gotcha – String is not Printable. You can use toString(thing) to convert anything to a string, and it will use Printable where available (and do nothing to convert strings), though this does have the side-effect of giving you a default for non-printable types that you may not want depending on your need.
Note you can require convertibility from something via a protocol:
protocol ConvertibleFromInt {
init(Int)
}
extension String: ConvertibleFromInt { }
extension UInt64: ConvertibleFromInt { }
func gimmeFromInt<T: ConvertibleFromInt>(i: Int) -> T {
return T(i)
}
let s: String = gimmeFromInt(5)
let ui: UInt64 = gimmeFromInt(5)