Swift Type Inference and Protocols with Associated Types - swift

EDIT: I can't understand why in the where clause below - where U.CacheType == T in AnyCacheable class Swift doesn't treat that statement as a constraint but simply sets T to U.CacheType. Type inference is the worst when things aren't readily apparent :-)
I am trying to follow Swift's Type erasure discussed here -
Specifically the code below:
protocol Cacheable {
associatedtype CacheType
func decode(_ data:Data) ->CacheType?
func encode()->Data?
}
extension String:Cacheable {
func decode(_ data:Data)->String? {
let string = String(data: data, encoding: .utf8)
return string
}
func encode()->Data? {
return data(using: .utf8)
}
}
class AnyCacheable<T>:Cacheable {
private let _encode:()->Data?
private let _decode:(_ data:Data)->T?
init<U:Cacheable>(_ cacheable:U) where U.CacheType == T {
self._encode = cacheable.encode
self._decode = cacheable.decode
}
func decode(_ data:Data)->T? {
return _decode(data)
}
func encode() -> Data? {
return _encode()
}
}
It works perfectly fine if I create a new instance of AnyCacheable as -
let cacheable:AnyCacheable = AnyCacheable("Swift")
I don't need to explicitly specify the concrete type of 'T' like let cacheable:AnyCacheable = AnyCacheable<String>("Swift")
How does Swift infer the concrete type for 'T'? From the initializer -
init<U:Cacheable>(_ cacheable:U) where U.CacheType == T {
self._encode = cacheable.encode
self._decode = cacheable.decode
}
I can see that Swift can infer the type for 'U' from the initializer argument (in this case a String type). In the where clause 'T' is on rhs. So how does that expression evaluate to true?

String is a Cacheable and its decode returns a String, so its associated type CacheType must be String.
AnyCacheable is initialized with a String, which is a Cacheable as required; so its U is String. But U.CacheType is T. So T is String.
To see that this is so, change the definition of String's adoption of Cacheable to this:
extension String:Cacheable {
func decode(_ data:Data)->Int? {
return 42
}
func encode()->Data? {
return data(using: .utf8)
}
}
Now compile your code and look to see what type you get in the line
let cacheable:AnyCacheable = AnyCacheable("Swift")
It is AnyCacheable<Int>.

Related

Is it possible to type-check against a list of types?

I know that it's possible to check if one value is an instance of a potential supertype:
func isInstance<T, U>(_ instance: T, of: U.Type) -> Bool {
return instance is U
}
However, what if you want to check against an entire array? Since you can't have an array of generics, the above approach doesn't really work. What I want to do is something like:
func isInstance<T>(_ instance: T, of types: [Any.Type]) -> Bool {
return types.allSatisfy { instance is $0 }
}
However, a variable (such as $0) isn't allowed as the RHS of an is expression. Is this kind of type check possible?
I saw one type check that used Mirror(reflecting:) and superclassMirror to search the inheritance hierarchy, but that only works for an array of classes, and I need this check to work when the array contains protocols as well.
It is possible, but only for classes and #objc protocols, and only if the object conforms to NSObjectProtocol. It is not possible in the general case for Swift protocols or value types.
import ObjectiveC
func isInstance(_ instance: some NSObjectProtocol, of types: [Any.Type]) -> Bool {
return types.allSatisfy { type in
if let `class` = type as? AnyClass {
return instance.isKind(of: `class`)
} else if let `protocol` = type as AnyObject as? Protocol {
return instance.conforms(to: `protocol`)
} else {
print("Cannot type-check instance against \(type)")
return false
}
}
}

Convert Swift Encodable class typed as Any to dictionary

In connection with my previous questions, I decided to subclass NSArrayController in order to achieve the desired behavior.
class NSPresetArrayController: NSArrayController {
override func addObject(_ object: Any) {
if let preset = object as? Preset {
super.addObject(["name": preset.name, "value": preset.value])
} else {
super.addObject(object)
}
}
}
This works, but what if I wanted something that works for any Encodable class, and not just one with two properties called name and value?
Basically, the problem is creating a dictionary from a class, where the keys are the property names, and the values are the values of these properties.
I tried writing something like this:
class NSPresetArrayController: NSArrayController {
override func addObject(_ object: Any) {
if let encodableObject = object as? Encodable {
let data = try! PropertyListEncoder().encode(encodableObject)
let any = try! PropertyListSerialization.propertyList(from: data, options: [], format: nil)
super.addObject(any)
}
}
}
However, I get a compile error:
Cannot invoke 'encode' with an argument list of type '(Encodable)'
1. Expected an argument list of type '(Value)'
How do I fix this so it compiles?
The problem is that protocols don't always conform to themselves. PropertyListEncoder's encode(_:) method expects a Value : Encodable argument:
func encode<Value : Encodable>(_ value: Value) throws -> Data
However the Encodable type itself is currently unable to satisfy this constraint (but it might well do in a future version of the language).
As explored in the linked Q&A (and also here), one way to work around this limitation is to open the Encodable value in order to dig out the underlying concrete type, which we can substitute for Value. We can do this with a protocol extension, and use a wrapper type in order to encapsulate it:
extension Encodable {
fileprivate func openedEncode(to container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
struct AnyEncodable : Encodable {
var value: Encodable
init(_ value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try value.openedEncode(to: &container)
}
}
Applied to your example:
class NSPresetArrayController : NSArrayController {
override func addObject(_ object: Any) {
guard let object = object as? Encodable else {
// Not encodable, maybe do some error handling.
return
}
do {
let encoded = try PropertyListEncoder().encode(AnyEncodable(object))
let cocoaPropertyList = try PropertyListSerialization.propertyList(from: encoded, format: nil)
super.addObject(cocoaPropertyList)
} catch {
// Couldn't encode. Do some error handling.
}
}
}
The type of the value that you pass to encode(_:) has to be a concrete type that implements Encodable. This means you need to recover the object's real type from the Any that you have. In order to cast, you must have a statically-specified type to which you are casting. You can't say object as! type(of: object), in other words; you have to say object as? MyClass (or in a generic context you can say object as? T).
Therefore, I believe that the only way to get around this is to statically enumerate the types you are working with, like so:
import Foundation
struct S : Encodable {
let i: Int
}
struct T : Encodable {
let f: Float
}
struct U : Encodable {
let b: Bool
}
func plistObject(from encodable: Any) -> Any? {
let encoded: Data?
switch encodable {
case let s as S:
encoded = try? PropertyListEncoder().encode(s)
case let t as T:
encoded = try? PropertyListEncoder().encode(t)
case let u as U:
encoded = try? PropertyListEncoder().encode(u)
default:
encoded = nil
}
guard let data = encoded else { return nil }
return try? PropertyListSerialization.propertyList(from: data,
options: [],
format: nil)
}
Needless to say, this is rather gross. It's inflexible, repetitive boilerplate. I'm not sure I can actually recommend its use. It's an answer to the literal question, not necessarily a solution to the problem.

Protocol extension with Optional AssociatedType [duplicate]

I have been trying to extract non-nil values from the String array. Like below. But, my senior wants it to be able to extract non-nil values from other types too.
I read, generics could help me for handling different types. How can I use generics so that I get to use following like extension to work with other types too?
getNonNil must return the extracted non-nil values of the specific type (i.e. if array is [String?] it must return [String], returns [Int] if [Int?])
Because I have to do further calculations.
What I have tried is below:
import Foundation
// Extended the collection-type so that collectiontype is constrained to having element with optional strings
extension CollectionType where Self.Generator.Element == Optional<String>{
func getNonNil() -> [String] {
// filter out all nil elements and forcefully unwrap them using map
return self.filter({$0 != nil}).map({$0!})
}
}
// Usage
let x: [String?] = ["Er", "Err", nil, "errr"]
x.getNonNil().forEach { (str) in
print(str)
}
For getNonNil you could simply use
x.flatMap { $0 }
// returns ["Er", "Err", "errr"] which is [String]
For the original question, typically you could introduce a protocol to the Optional type (e.g. via the muukii/OptionalProtocol package):
protocol OptionalProtocol {
associatedtype Wrapped
var value: Wrapped? { get }
}
extension Optional: OptionalProtocol {
public var value: Wrapped? { return self }
}
extension CollectionType where Self.Generator.Element: OptionalProtocol {
func getNonNil() -> [Self.Generator.Element.Wrapped] {
...
}
}
There's no easy way of achieving this through an extension, as you cannot introduce new generic types into extensions (although this is part of the Swift Generics Manifesto – so may well be possibly in a future version of Swift).
As #kennytm says, the simplest solution is just to use flatMap, which filters out nil:
x.flatMap{$0}.forEach { (str) in
print(str)
}
If however, you still want this as an extension, you could use a protocol workaround in order to allow you to constrain the extension to any optional element type (Swift 3):
protocol _OptionalProtocol {
associatedtype Wrapped
func _asOptional() -> Wrapped?
}
extension Optional : _OptionalProtocol {
func _asOptional() -> Wrapped? {return self}
}
extension Collection where Self.Iterator.Element : _OptionalProtocol {
func getNonNil() -> [Iterator.Element.Wrapped] {
return flatMap{$0._asOptional()}
}
}
...
let x : [String?] = ["Er", "Err", nil, "errr"]
x.getNonNil().forEach { (str) in
print(str)
}
(In Swift 3, CollectionType has been renamed to Collection, and Generator is now Iterator)
Although flatMap is almost certainly preferred in this situation, I'm only really adding this for the sake of completion.
The easiest approach is using flatMap as kennytm suggested, but if you absolutely want to know how to create such a method using generics, one approach would be to create a global method that takes in the collection as a parameter:
public func getNonNil<T, C: CollectionType where C.Generator.Element == Optional<T>>(collection: C) -> [T] {
return collection.filter({$0 != nil}).map({$0!})
}
let x: [String?] = ["Er", "Err", nil, "errr"]
print(getNonNil(x)) // returns ["Er", "Err", "errr"]

Using a Type Variable in a Generic

I have this question except for Swift. How do I use a Type variable in a generic?
I tried this:
func intType() -> Int.Type {
return Int.self
}
func test() {
var t = self.intType()
var arr = Array<t>() // Error: "'t' is not a type". Uh... yeah, it is.
}
This didn't work either:
var arr = Array<t.Type>() // Error: "'t' is not a type"
var arr = Array<t.self>() // Swift doesn't seem to even understand this syntax at all.
Is there a way to do this? I get the feeling that Swift just doesn't support it and is giving me somewhat ambiguous error messages.
Edit: Here's a more complex example where the problem can't be circumvented using a generic function header. Of course it doesn't make sense, but I have a sensible use for this kind of functionality somewhere in my code and would rather post a clean example instead of my actual code:
func someTypes() -> [Any.Type] {
var ret = [Any.Type]()
for (var i = 0; i<rand()%10; i++) {
if (rand()%2 == 0){ ret.append(Int.self) }
else {ret.append(String.self) }
}
return ret
}
func test() {
var ts = self.someTypes()
for t in ts {
var arr = Array<t>()
}
}
Swift's static typing means the type of a variable must be known at compile time.
In the context of a generic function func foo<T>() { ... }, T looks like a variable, but its type is actually known at compile time based on where the function is called from. The behavior of Array<T>() depends on T, but this information is known at compile time.
When using protocols, Swift employs dynamic dispatch, so you can write Array<MyProtocol>(), and the array simply stores references to things which implement MyProtocol — so when you get something out of the array, you have access to all functions/variables/typealiases required by MyProtocol.
But if t is actually a variable of kind Any.Type, Array<t>() is meaningless since its type is actually not known at compile time. (Since Array is a generic struct, the compiler needs know which type to use as the generic parameter, but this is not possible.)
I would recommend watching some videos from WWDC this year:
Protocol-Oriented Programming in Swift
Building Better Apps with Value Types in Swift
I found this slide particularly helpful for understanding protocols and dynamic dispatch:
There is a way and it's called generics. You could do something like that.
class func foo() {
test(Int.self)
}
class func test<T>(t: T.Type) {
var arr = Array<T>()
}
You will need to hint the compiler at the type you want to specialize the function with, one way or another. Another way is with return param (discarded in that case):
class func foo() {
let _:Int = test()
}
class func test<T>() -> T {
var arr = Array<T>()
}
And using generics on a class (or struct) you don't need the extra param:
class Whatever<T> {
var array = [T]() // another way to init the array.
}
let we = Whatever<Int>()
jtbandes' answer - that you can't use your current approach because Swift is statically typed - is correct.
However, if you're willing to create a whitelist of allowable types in your array, for example in an enum, you can dynamically initialize different types at runtime.
First, create an enum of allowable types:
enum Types {
case Int
case String
}
Create an Example class. Implement your someTypes() function to use these enum values. (You could easily transform a JSON array of strings into an array of this enum.)
class Example {
func someTypes() -> [Types] {
var ret = [Types]()
for _ in 1...rand()%10 {
if (rand()%2 == 0){ ret.append(.Int) }
else {ret.append(.String) }
}
return ret
}
Now implement your test function, using switch to scope arr for each allowable type:
func test() {
let types = self.someTypes()
for type in types {
switch type {
case .Int:
var arr = [Int]()
arr += [4]
case .String:
var arr = [String]()
arr += ["hi"]
}
}
}
}
As you may know, you could alternatively declare arr as [Any] to mix types (the "heterogenous" case in jtbandes' answer):
var arr = [Any]()
for type in types {
switch type {
case .Int:
arr += [4]
case .String:
arr += ["hi"]
}
}
print(arr)
I would break it down with the things you already learned from the first answer. I took the liberty to refactor some code. Here it is:
func someTypes<T>(t: T.Type) -> [Any.Type] {
var ret = [Any.Type]()
for _ in 0..<rand()%10 {
if (rand()%2 == 0){ ret.append(T.self) }
else {
ret.append(String.self)
}
}
return ret
}
func makeArray<T>(t: T) -> [T] {
return [T]()
}
func test() {
let ts = someTypes(Int.self)
for t in ts {
print(t)
}
}
This is somewhat working but I believe the way of doing this is very unorthodox. Could you use reflection (mirroring) instead?
Its possible so long as you can provide "a hint" to the compiler about the type of... T. So in the example below one must use : String?.
func cast<T>(_ value: Any) -> T? {
return value as? T
}
let inputValue: Any = "this is a test"
let casted: String? = cast(inputValue)
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
Why Swift doesn't just allow us to let casted = cast<String>(inputValue) I'll never know.
One annoying scenerio is when your func has no return value. Then its not always straightford to provide the necessary "hint". Lets look at this example...
func asyncCast<T>(_ value: Any, completion: (T?) -> Void) {
completion(value as? T)
}
The following client code DOES NOT COMPILE. It gives a "Generic parameter 'T' could not be inferred" error.
let inputValue: Any = "this is a test"
asyncCast(inputValue) { casted in
print(casted)
print(type(of: casted))
}
But you can solve this by providing a "hint" to compiler as follows:
asyncCast(inputValue) { (casted: String?) in
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
}

testing protocol conformance with associated types

I have a protocol that uses an associated type, as such:
protocol Populatable {
typealias T
func populateWith(object: T)
}
and classes that implement the protocol:
class DateRowType: Populatable {
func populateWith(object: NSDate) {
print(object.description)
}
}
class StringRowType : Populatable {
func populateWith(object: String) {
print(object)
}
}
but when I try to cast or test for conformance, like this:
let drt = DateRowType()
let srt = StringRowType()
let rowTypes = [drt, srt]
let data = [NSDate(), "foo"]
for (i, p: Populatable) in enumerate(rowTypes) {
p.populateWith(data[i])
}
I get the error:
Protocol 'Populatable' can only be used as a generic constraint because it has Self or associated type requirements
What's the correct way to test if the object conforms to the Populatable protocol?
Note: all the code required to try this out is contained in the question, just copy the code blocks into a playground.
As the error says, you cannot cast it to Populatable here. I think the correct way is to cast it to EventRowType.
if let rowController = self.table.rowControllerAtIndex(i) as? EventRowType {
And you already tested that 'EventRowType' class conforms 'Populatable' protocol. Because if the EventRowType doesn't have function named 'populate', swift compiler says,
Type 'EventRowType' does not conform to protocol 'Populatable'
I don't think you will be able to go generic the whole way, unless possibly by using AnyObject and testing the class of the parameter in each populateWith function.
But this will work:
for (i, p) in enumerate(rowTypes) {
if let dateRow = p as? DateRowType {
dateRow.populateWith(data[i] as! NSDate)
}
else if let stringRow = p as? StringRowType {
stringRow.populateWith(data[i] as! String)
}
}
You will just need to expand this for every Populatable class you add.