Determine Collection type in Swift - swift

I've a function whose header looks like this
func doSomethingOnCollection<T:Collection>(_ array: T) -> [T]
as you can see it takes Collection as a parameter, meaning it can be Array, Set or Dictionary, how can I check type of parameter passed in this function at runtime ?

Swift documentation says:
Use the type check operator (is) to check whether an instance is of a
certain subclass type. The type check operator returns true if the
instance is of that subclass type and false if it is not.
func doSomethingOnCollection<T: Collection>(_ param: T) -> [T] {
if param is Array<Any> {
print("Array")
}
// We can't say 'Set<Any>', since type of set should conform
// protocol 'Hashable'
else if param is Set<Int> {
print("Set")
}
// For the same reason above, we can't say 'Dictionary<Any, Any>'
else if param is Dictionary<Int, Any> {
print("Dictionary")
}
return []
}

Related

Reference to generic function in Swift

In Swift, you can create a reference to a function in the form of a closure. For example:
func simpleFunc(param: Int) {
}
let simpleFuncReference = simpleFunc(param:) // works just fine
But in one case, I have a function with a generic parameter like this:
func hardFunc<T: StringProtocol>(param: T) {
}
let hardFuncReference = hardFunc(param:) // "Generic parameter 'T' could not be inferred"
To try to remove that error, I attempted to explicitly specify the type, but immediately another error comes up.
func hardFunc<T: StringProtocol>(param: T) {
}
let hardFuncReference = hardFunc(param:) // "Cannot explicitly specialize a generic function"
Is there a way I can get a reference to hardFunc as a closure?
As you already guessed, you have to help type inference out a little:
func hardFunc<T: StringProtocol>(param: T) {
}
let hardFuncReference:(String) -> Void = hardFunc(param:)
Note that you do have to specify the particular type that you're specializing on, but in this case you do it by specifying the type of the variable you're assigning the closure to.
You can't keep it generic unless you're in a generic context specifying that it's generic on the same type. So this will work too
struct Foo<T: StringProtocol> {
let hardFuncReference:(T) -> Void = hardFunc(param:)
}

Returning the Concrete Type Based on Protocol in Swift

I have following code:
protocol Vehicle {
func start()
}
class Car: Vehicle {
func start() {
print("Start car")
}
}
class MotorCycle: Vehicle {
func start() {
print("Start MotorCycle")
}
}
let vehicles: [Vehicle] = [Car(), MotorCycle()]
func get<T: Vehicle>() -> some Vehicle {
let result = vehicles.first {
$0 === T.self
}
return result!
}
// so I should be able to do this!
let car = get<Car>().start()
Inside the get function I want to go iterate through vehicles and return the concrete type of the Vehicle, which is either Car or MotorCycle. Finally, I want to call start on the returned type. How can I achieve it?
This is how get should be written:
func get<T: Vehicle>(_ type: T.Type) -> T? {
vehicles.first(where: { $0 is T }) as? T
}
There might not be any Ts in vehicles, so we should return an optional T.
For the implementation, you should use is to check the type in the first(where:) closure. Then, cast to T.
You can't directly pass type arguments like <Car> to a function in Swift. Type arguments must always be inferred. So I used a formal parameter type to help Swift infer what T is.
Caller:
get(Car.self)?.start()
You can not compare an instance value with a type like this: $0 === T.self. You can use $0 is T instead.
And, when calling a generic function, you cannot explicitly specialize a generic function. The type has to be inferred, like #Sweeper said.

Cannot convert value type... when using generics and polymorphism in Swift 3

In my simplified example I'm getting the error: Cannot convert value of type 'Foo' to expected argument type BaseItem<Any>
But the class Foo extends BaseItem<String>.
This is the example code:
class BaseItem<T> {
var param: T?
}
class Foo: BaseItem<Int> {
}
func checkItem(item: BaseItem<Any>) -> Bool{
return item.param != nil;
}
I get the error when calling
checkItem(item: Foo())
What am I missing?
You need to define your checkItem function in terms of generics too:
func checkItem<T>(item: BaseItem<T>) -> Bool {
return item.param != nil
}
Gotta define checkItem function with generics too.
func checkItem<T>(item: BaseItem<T>) -> Bool {
return item.param != nil
}
The problem is that generics are invariant – consider if your checkItem(item:) function had said:
func checkItem(item: BaseItem<Any>) {
item.param = "foo"
}
That would be illegal for a BaseItem<Int>, as you cannot possibly assign a String instance to an Int? property – which is why it (an instance of Foo) cannot be typed as a BaseItem<Any>.
The solution, as other answers have said, is to use a generic placeholder for the function:
func checkItem<T>(item: BaseItem<T>) -> Bool {
return item.param != nil
}
Now, rather than saying that you're taking a BaseItem<Any>, that has a param of type Any? (can be assigned a value of any type) – you're now saying that you're taking a BaseItem with any specific placeholder type; which will be satisfied at the call-site of the function.
The function implementation itself therefore cannot make any assumptions about this type, and will disallow the assignment of an arbitrary value to param. The compiler will only allow an assignment of a value of type T.
The signature of checkItem function should be: checkItem<T>(item: BaseItem<T>) -> Bool, as follows:
func checkItem<T>(item: BaseItem<T>) -> Bool {
return item.param != nil
}
Usage:
checkItem(item: Foo()) // false
let myFoo = Foo()
myFoo.param = 0
checkItem(item: myFoo) // true
The reason of why the compiler complains about
Cannot convert value of type 'Foo' to expected argument type
BaseItem
is that you are trying to pass BaseItem<Int> instance as BaseItem<Any> which is invalid (Any data type is not T generic type).

Swift: convert array of actual type to array of protocol type

Say you have a protocol ToString that is implemented for Int, and a function that takes an array of ToStrings.
Trying to pass an array of Ints to this function results in the error Cannot convert value of type '[Int]' to expected argument type '[ToString]'.
However, using map on the array before passing it to the function works. Is this the supposed way to do the type cast or is there a way that doesn't result in iterating over the array? Or is this optimized out by the compiler?
Full example:
protocol ToString {
func toString() -> String
}
extension Int: ToString {
func toString() -> String {
return "\(self)"
}
}
func log( values: [ToString]) {
values.forEach { print( $0.toString()) }
}
let values: [Int] = [1, 2, 3]
// Error: Cannot convert value of type '[Int]' to expected argument type '[ToString]'
log( values)
// No error
log( values.map { $0 })
This Q&A explains the problem. I will suggest a fix that avoids creation of a new array: make your log function generic, and add a type constraint on its type parameter, requiring it to conform to ToString protocol:
func log<T:ToString>( values: [T]) {
values.forEach { print( $0.toString()) }
}
Now Swift lets you call your function with arrays of any type, as long as array elements conform to ToString protocol.

Generic function taking a type name in Swift

In C#, it's possible to call a generic method by specifying the type:
public T f<T>()
{
return something as T
}
var x = f<string>()
Swift doesn't allow you to specialize a generic method when calling it. The compiler wants to rely on type inference, so this is not possible:
func f<T>() -> T? {
return something as T?
}
let x = f<String>() // not allowed in Swift
What I need is a way to pass a type to a function and that function returning an object of that type, using generics
This works, but it's not a good fit for what I want to do:
let x = f() as String?
EDIT (CLARIFICATION)
I've probably not been very clear about what the question actually is, it's all about a simpler syntax for calling a function that returns a given type (any type).
As a simple example, let's say you have an array of Any and you create a function that returns the first element of a given type:
// returns the first element in the array of that type
func findFirst<T>(array: [Any]) -> T? {
return array.filter() { $0 is T }.first as? T
}
You can call this function like this:
let array = [something,something,something,...]
let x = findFirst(array) as String?
That's pretty simple, but what if the type returned is some protocol with a method and you want to call the method on the returned object:
(findFirst(array) as MyProtocol?)?.SomeMethodInMyProtocol()
(findFirst(array) as OtherProtocol?)?.SomeMethodInOtherProtocol()
That syntax is just awkward. In C# (which is just as strongly typed as Swift), you can do this:
findFirst<MyProtocol>(array).SomeMethodInMyProtocol();
Sadly, that's not possible in Swift.
So the question is: is there a way to accomplish this with a cleaner (less awkward) syntax.
Unfortunately, you cannot explicitly define the type of a generic function (by using the <...> syntax on it). However, you can provide a generic metatype (T.Type) as an argument to the function in order to allow Swift to infer the generic type of the function, as Roman has said.
For your specific example, you'll want your function to look something like this:
func findFirst<T>(in array: [Any], ofType _: T.Type) -> T? {
return array.lazy.compactMap { $0 as? T }.first
}
Here we're using compactMap(_:) in order to get a sequence of elements that were successfully cast to T, and then first to get the first element of that sequence. We're also using lazy so that we can stop evaluating elements after finding the first.
Example usage:
protocol SomeProtocol {
func doSomething()
}
protocol AnotherProtocol {
func somethingElse()
}
extension String : SomeProtocol {
func doSomething() {
print("success:", self)
}
}
let a: [Any] = [5, "str", 6.7]
// Outputs "success: str", as the second element is castable to SomeProtocol.
findFirst(in: a, ofType: SomeProtocol.self)?.doSomething()
// Doesn't output anything, as none of the elements conform to AnotherProtocol.
findFirst(in: a, ofType: AnotherProtocol.self)?.somethingElse()
Note that you have to use .self in order to refer to the metatype of a specific type (in this case, SomeProtocol). Perhaps not a slick as the syntax you were aiming for, but I think it's about as good as you're going to get.
Although it's worth noting in this case that the function would be better placed in an extension of Sequence:
extension Sequence {
func first<T>(ofType _: T.Type) -> T? {
// Unfortunately we can't easily use lazy.compactMap { $0 as? T }.first
// here, as LazyMapSequence doesn't have a 'first' property (we'd have to
// get the iterator and call next(), but at that point we might as well
// do a for loop)
for element in self {
if let element = element as? T {
return element
}
}
return nil
}
}
let a: [Any] = [5, "str", 6.7]
print(a.first(ofType: String.self) as Any) // Optional("str")
What you probably need to do is create a protocol that looks something like this:
protocol SomeProtocol {
init()
func someProtocolMethod()
}
And then add T.Type as a parameter in your method:
func f<T: SomeProtocol>(t: T.Type) -> T {
return T()
}
Then assuming you have a type that conforms to SomeProtocol like this:
struct MyType: SomeProtocol {
init() { }
func someProtocolMethod() { }
}
You can then call your function like this:
f(MyType.self).someProtocolMethod()
Like others have noted, this seems like a convoluted way to do what you want. If you know the type, for example, you could just write:
MyType().someProtocolMethod()
There is no need for f.