Why can't Swift infer this return type properly? - swift

I'm trying to do something I think should be pretty simple, but I'm running into trouble with Swift's type inference. I really don't understand why it's falling down here.
I have a type Cocktail, which has other properties, but the only one important here is the name:
struct Cocktail {
// ... other stuff
let name: String
}
Then I have two protocols:
protocol ScrollIndexable {
var scrollIndexTitle: String { get }
}
protocol ScrollIndexProviding {
var scrollIndices: [any ScrollIndexable] { get }
}
along with a simple conformance on String to ScrollIndexable:
extension String: ScrollIndexable {
var scrollIndexTitle: String { self }
}
I want to make it so that I can use an array of Cocktails as a ScrollIndexProviding:
extension Array: ScrollIndexProviding where Element == Cocktail {
var scrollIndices: [any ScrollIndexable] {
let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
guard let firstCharacter = cocktail.name.first else {
return
}
partialResult.insert(String(firstCharacter))
}
// The return line here has two errors:
// Cannot convert return expression of type 'Array<Cocktail>' to return type '[any ScrollIndexable]'
// No exact matches in call to initializer
return Array(firstCharacters)
}
}
This extension fails to build, with two errors:
Cannot convert return expression of type 'Array' to return type '[any ScrollIndexable]'
No exact matches in call to initializer
The second error seems like noise to me, since Set conforms to Sequence, so I should be able to use that init method.
The first error is confusing to me since the firstCharacters array is of type Set<String>, so the error message just doesn't seem to make any sense. Is there something I'm misunderstanding about the any keyword here? What's going on?

The issue is that you're inside an extension of Array where the Element is Cocktail, so when you try to create an array without specifying the element type the compiler will assume you mean for the element type to be Cocktail.
extension Array where Element: Cocktail {
func someMethod() {
// This array is of type `Array<Cocktail>` since the compiler
// assumes the array's element type should be the same as
// Self's element type, which (from the extension) is `Cocktail`.
let array = Array()
}
}
So, to fix this, just explicitly tell the compiler that the array's element type is String, as in:
extension Array: ScrollIndexProviding where Element == Cocktail {
var scrollIndices: [any ScrollIndexable] {
let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
guard let firstCharacter = cocktail.name.first else {
return
}
partialResult.insert(String(firstCharacter))
}
return Array<String>(firstCharacters)
// ^^^^^^^^ add this
}
}

Related

Swift: Same-Type requirement makes generic parameters equivalent?

I'm using swift 5 and try to compile the following code:
protocol BasicProtocol {
associatedtype T
var str: T {get set}
}
struct AItem<U>: BasicProtocol {
typealias T = U
var str: T
init<G: StringProtocol>(str: G) where G == T {
self.str = str
}
}
I got compilation error:
error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
init<G: StringProtocol>(str: G) where G == T {
^
How to make them equivalent? or I can't?
Thanks.
Update 1:
This is the problem I encountered: I want to declare struct "AItem", hoping it has a generic type "T". And this generic type will have some restrictions, such as: "T: StringProtocol". Then for some reason, I need to use an array to load these structs, and ensure that the generics of each structure can be set at will.
I learned that there is "type-erase" might can solve this. So I tried this way, but it seemed unsuccessful. The problems mentioned above have occurred.
Update 2:
struct AItem<T: StringProtocol> {
var aStr: T
}
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
Look,If you compile this code, you will get a compilation error:
error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
^
If I use "var array: [AItem<String>]", I will not be able to put any other non-"String" but implemented "StringProtocol" instance in the array.
This is why I said I want "ensure that the generics of each structure can be set at will".
Update 3:
very thanks for #jweightman, now I update my question again.
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
.......
struct AItem<T = which class has Implemented "ConstraintProtocol"> {
var aPara: T
init(aPara:T) {
self.aPara = aPara
}
}
// make a array to contain them
var anArray: [AItem<Any class which Implemented "ConstraintProtocol">] = [AItem(aPara: "String"), AItem(aPara: 1234), AItem(aPara: Data("a path")), …]
// then I can use any item which in anArray. Maybe I will implement a method to judge these generics and perform the appropriate action.
for curItem in anArray {
var result = handleItem(curItem)
do something...
}
func handleItem<T: ConstraintProtocol>(item: AItem<T>) -> Any? {
if (item.T is ...) {
do someThing
return ......
} else if (item.T is ...) {
do someThing
return ...
}
return nil
}
This is my whole idea, but all of which are pseudo-code.
It seems like type erasure is the answer to your problem. The key idea to the type erasure pattern is to put your strongly typed but incompatible data (like an AItem<String> and an AItem<Data>) inside of another data structure which stores them with "less precise" types (usually Any).
A major drawback of type erasure is that you're discarding type information—if you need to recover it later on to figure out what you need to do with each element in your array, you'll need to try to cast your data to each possible type, which can be messy and brittle. For this reason, I've generally tried to avoid it where possible.
Anyways, here's an example of type erasure based on your pseudo code:
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
struct AItem<T: ConstraintProtocol> {
var aPara: T
init(aPara: T) {
self.aPara = aPara
}
}
struct AnyAItem {
// By construction, this is always some kind of AItem. The loss of type
// safety here is one of the costs of the type erasure pattern.
let wrapped: Any
// Note: all the constructors always initialize `wrapped` to an `AItem`.
// Since the member variable is constant, our program is "type correct"
// even though type erasure isn't "type safe."
init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
self.wrapped = wrapped
}
init<T: ConstraintProtocol>(aPara: T) {
self.wrapped = AItem(aPara: aPara);
}
// Think about why AnyAItem cannot expose any properties of `wrapped`...
}
var anArray: [AnyAItem] = [
AnyAItem(aPara: "String"),
AnyAItem(aPara: 1234),
AnyAItem(aPara: "a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
if (item.wrapped is AItem<String>) {
return "String"
} else if (item.wrapped is AItem<Data>) {
return "Data"
} else if (item.wrapped is AItem<Int>) {
return "Int"
}
return "unknown"
}
An alternative to type erasure you could consider, which works well if there's a small, finite set of concrete types your generic could take on, would be to use an enum with associated values to define a "sum type". This might not be a good choice if the protocol you're interested in is from a library that you can't change. In practice, the sum type might look like this:
enum AItem {
case string(String)
case data(Data)
case int(Int)
}
var anArray: [AItem] = [
.string("String"),
.int(1234),
.data("a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
func handleItem(item: AItem) -> String {
// Note that no casting is required, and we don't need an unknown case
// because we know all types that might occur at compile time!
switch item {
case .string: return "String"
case .data: return "Data"
case .int: return "Int"
}
}

RxSwift protocol and concrete types

I'm having trouble with a BehaviorRelay that has a protocol type and using it on concrete types. Here's my code:
protocol Item {
var title: { get }
}
struct Can: Item {
let title = "Can"
}
let canRelay = BehaviorRelay<Can?>(value: nil)
func handle(item: BehaviorRelay<Item?>) {
// do something with item here
}
handle(item: canRelay) // can't do this?
I assumed I would be able to call handle(item:) but it's not the case because the arguments don't match. I get that they don't match, but Can is a type of Item so shouldn't this be possible?
Can may be a subtype of Item, but BehaviorRelay<Can> is not a subtype of BehaviorRelay<Item>.
Also, you should not be passing BehaviorRelays around in code. Pass Observables instead.
Once you know these two rules, you end up with:
func handle(item: Observable<Item?>) {
// do something with item here
}
handle(item: canRelay.map { $0 })

Instance method 'drive' requires the types 'NotificationItem' and '[NotificationItem]' be equivalent

I have create a class called notification Item and parsing the data from model class RTVNotification
import Foundation
import RTVModel
public class NotificationItem: NSObject {
public var id: String
public var title: String
public var comment: String
public var publishStartDateString: String
init(id: String,
title: String,
comment: String,
publishStartDateString: String) {
self.id = id
self.title = title
self.comment = comment
self.publishStartDateString = publishStartDateString
super.init()
}
}
extension NotificationItem {
static func instantiate(with notification: RTVNotification) -> NotificationItem? {
return NotificationItem(
id: notification.id,
title: notification.title,
comment: notification.comment,
publishStartDateString: notification.publishStartDateString)
}
}
ViewModel
public class SettingsViewModel: ViewModel {
var item = [NotificationItem]()
public var fetchedNotifications: Driver<NotificationItem> = .empty()
public var apiErrorEvents: Driver<RTVAPIError> = .empty()
public var notificationCount: Driver<Int> = .empty()
public func bindNotificationEvents(with trigger: Driver<Void>) {
let webService: Driver<RTVInformationListWebService> = trigger
.map { RTVInformationListParameters() }
.webService()
let result = webService.request()
apiErrorEvents = Driver.merge(apiErrorEvents, result.error())
notificationCount = result.success().map {$0.informationList.maxCount }
fetchedNotifications =
result.success()
.map {$0.informationList.notifications}
-----> .map {NotificationItem.instantiate(with: $0)}
}
}
Getting an Error saying that Cannot convert value of type '[RTVNotification]' to expected argument type 'RTVNotification'
What can i do to solve this.
The purpose of the map() function is to iterate over the elements of an input array and apply a transform function to each of those elements. The transformed elements are added to a new output array that is returned by map(). It's important to understand that the length of the output array is the same length as the input array.
For example:
let inputArray = ["red", "white", "blue"]
let outputArray = inputArray.map { $0.count } // outputArray is [3, 5, 4]
In your code, you are calling:
result.success().map { $0.informationList.notifications }
I don't know RxSwift at all, so I'm going to go into wild speculation here.
First, I don't know exactly what result.success() returns, but the fact you can call map() on it implies result.success() returns an array (which is weird, but ok we'll go with it).
Second, we know the array returned by result.success() contains elements that have an informationList property, and the informationList property has a property called notifications. My guess is that notifications, being plural, means the notifications property type is an array, probably [RTVNotification].
So this code:
result.success().map { $0.informationList.notifications }
Transforms the success() array into a new array. Based on my assumption that notifications is of type [RTVNotification], and further assuming the success() array contains only one element, I would expect the result of
result.success().map { $0.informationList.notifications }
To be an array of type [[RTVNotification]], i.e. an array with one element, where that element is an array of RTVNotifications.
You then feed that [[RTVNotification]] array into another map() function:
.map { NotificationItem.instantiate(with: $0) }
Recall from the start of this answer that map() iterates over the elements of arrays. Since the input array to this map is [[RTVNotification]], its elements will be of type [RTVNotification]. That's what the $0 is in your call - [RTVNotification]. But the instantiate(with:) function takes an RTVNotification, not an array of RTVNotification, thus you get the error:
Cannot convert value of type '[RTVNotification]' to expected argument type 'RTVNotification'
So what can you do to fix it?
I would probably do something like this (you'll have to tailor it to your use case):
guard let successResponse = webService.request().success().first else {
print("no success response received")
return nil // basically report up an error here if not successful
}
// get the list of notifications, this will be type [RTVNotification]
let notifications = successResponse.informationList.notifications
// Now you can create an array of `[NotificationItem]` like you want:
let notificationItems = notifications.map { NotificationItem.instantiate(with: $0) }
// do something with notificationItems...
The caveat to the above is if you need to iterate over each element in the success() array, then you could do that like this:
let successResponses = webService.result().success()
// successNotifications is of type [[RTVNotification]]
let successNotifications = successResponses.map { $0.informationList.notifications }
// successItems will be of type [[NotificationItem]]
let successItems = successNotifications.map { notifications in
notifications.map { NotificationItem.instantiate(with: $0) }
}
In other words, in this last case, you get back an array that contains arrays of NotificationItem.
Your problem is here:
fetchedNotifications: Driver<NotificationItem> should be fetchedNotifications: Driver<[NotificationItem]> and the line .map {NotificationItem.instantiate(with: $0)} needs another map You are dealing with an Observable<Array<RTVNotification>>. You have a container type within a container type, so you need a map within a map:
.map { $0.map { NotificationItem.instantiate(with: $0) } }
When your types don't match, you need to change the types.
Other issues with your code...
Drivers, Observables, Subjects and Relays should never be defined with var, they should always be lets. Objects that subscribe to your properties before the bind is called will connect to the .empty() observables and never get any values. This is functional reactive programming, after all.
Your NotificationItem type should either be a struct or all it's properties should be `let's.
Be sure to read and understand #par's answer to this question. He wrote a really good explanation and it would be a shame to waste that knowledge transfer.

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>
}

'AnyObject' does not have a member named xxx

There is a method which receives an array of 'AnyObject'. Then inside the method based on some condition cast that AnyObject to specific object. There is no issue till this point. After cast the AnyObject into specific object, if i try to access it's properties then it throws error. I understand the issue for what reason the error is coming. But is there any other way to obtain the same in any other logic. Here is the code.
func downloadImage(#list: Array<AnyObject>, forControler: String) {
var xxxList: Array<AnyObject>
if forControler == "A" {
xxxList = list as! Array<A>
} else if forControler == "B" {
xxxList = list as! Array<B>
} else {
xxxList = list as! Array<C>
}
for (index, url) in enumerate(xxxList) {
url.A
}
}
Error throws in url.A
Thanks
You have multiple errors in your code.
First, when declaring a var in Swift (here xxxList), you have to initialize it just after. Otherwise, this var needs to be flagged as optional.
You get that error for the compiler is "dumb". It does not know what the real type of your objects wrapped into your array is. It only sees AnyObject objects, which do not have any A field or property. You have to cast your list into the desired type before iterating over it.
Your code should be like this instead:
func downloadImage(#list: [AnyObject], forController: String) {
if forController == "A" {
var aList = list as! [A]
// for loop iterating over A objects
} else if forController == "B" {
var bList = list as! [B]
// for loop iterating over B objects
} else {
var cList = list as! [C]
// for loop iterating over C objects
}
}
You must define a loop for each type. Well, you can also define a single loop and perform the casting into that one (moving the if statements into that loop).