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.
Swift 4s 'Arrays' and 'Dictionary' types belong to generic collections. With the help of arrays and dictionaries the arrays are defined to hold 'Int' values and 'String' values or any other types.
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")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With