There are a lot of answers surrounding my issue, but the solutions I have tried for them have not quite solved the problem. I'm not sure if it has something to do with having multiple generics or something else (also newer to Swift so still wrapping my head around syntax).
I noticed a lot of commonality in my API request code so I decided to abstract the main work out and have it work with any codable request/response objects. Here is my main request method:
private func sendRequest<T: Encodable, U: Decodable>(url: URL, requestModel: T) -> Promise<U>
And I am trying to call it as such:
public func signIn(requestModel: SignInRequest) -> Promise<SignInResponse> {
let url = URL(string: authURL + "/signin")!
//getting compile error "Cannot explicitly specialize generic function" on this line
return sendRequest<SignInRequest, SignInResponse>(url: url, requestModel: requestModel)
}
I've tried assigning directly to the return object:
let x : Promise<SignInResponse> = sendRequest(...)
return x
to help the compiler out (as suggested in other solutions) but still same issue. Any insights?
I ended up using a solution based on this post: https://stackoverflow.com/a/36232002/1088099
I explicitly passed in the request and response object types as parameters:
private func sendRequest<T: Encodable, U: Decodable>(requestModelType: T.Type, responseModelType: U.Type, url: URL, requestModel: T) -> Promise<U>
and calling:
public func signIn(requestModel: SignInRequest) -> Promise<SignInResponse> {
let url = URL(string: authURL + "/signin")!
return sendRequest(requestModelType:SignInRequest.self,
responseModelType:SignInResponse.self,
url: url,
requestModel: requestModel)
}
Not as clean of a solution I was hoping for, but works.
Inference will succeed if you give enough context with the function's arguments to determine all generic parameters.
From you code, I suspect this is not the case as neither does the generic type U neither appear as an argument, nor there is an additional type constraint to specify it. Therefore, the compiler can't infer what is the concrete type of your function's return value from a function call.
Hence, if there's a relationship between T and U, you may use it as an additional constraint to "help" the compiler infer U once it found T. Here is a minimal example:
protocol Foo {
associatedtype Bar
var bar: Bar { get }
}
struct FooImpl: Foo {
let bar: Int = 0
}
func f<T, U>(foo: T) -> U where T: Foo, U == T.Bar {
return foo.bar
}
let a = f(foo: FooImpl())
Protocol Foo has an associated type Bar. By using this association in the function f's signature to add a constraint on its return type, Swift's compiler is now able to fully infer the specialized signature of f when I call it with an instance of FooImpl.
In your particular example, I would suggest writing a protocol to which the requestModel parameter should confirm, with an associated type to place a constraint on the return value.
For instance:
protocol RequestModel: Encodable {
associatedtype ResponseModel: Decodable
}
struct AnAwesomeModel: RequestModel {
typealias ResponseModel = String
}
private func sendRequest<T: RequestModel, U>(url: URL, requestModel: T) -> Promise<U>
where U == T.ResponseModel
{
// ...
}
// x's type will be inferred as String
let x = sendRequest(url: URL("http://...")!, requestModel: AnAwesomeModel())
Related
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:)
}
I am attempting to create a function that can return any type. I do not want it to return an object of type Any, but of other types, i.e. String, Bool, Int, etc. You get the idea.
You can easily do this using generics in this fashion:
func example<T>(_ arg: T) -> T {
// Stuff here
}
But is it possible to do it without passing in any arguments of the same type? Here is what I am thinking of:
func example<T>() -> T {
// Stuff here
}
When I try to do this, everything works until I call the function, then I get this error:
generic parameter 'T' could not be inferred
is it possible to do it without passing in any arguments of the same type?
The answer is yes, but there needs to be a way for the compiler to infer the correct version of the generic function. If it knows what it is assigning the result to, it will work. So for instance, you could explicitly type a let or var declaration. The below works in a playground on Swift 3.
protocol Fooable
{
init()
}
extension Int: Fooable {}
extension String: Fooable {}
func foo<T: Fooable>() -> T
{
return T()
}
let x: String = foo() // x is assigned the empty string
let y: Int = foo() // y is assigned 0
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.
I declared a protocol with a generic function, but it seems that the type inference isn't working properly after implementing it.
protocol SearchableRealmModel {
static func search<Self: Object>(needle: String) -> Results<Self>?
}
class Thing: Object, SearchableRealmModel {
class func search<Thing>(needle: String) -> Results<Thing>? {
return realm()?.objects(Thing).filter("name == '\(needle)'")
}
}
let things = Thing.search("hello") // works but inferred type is Results<Object>?
The problem here is that the inferred type of things is Results<Object>?. I realize these variations can be used,
let things: Results<Thing>? = Thing.search("hello")
let things = Thing.search("hello") as Results<Thing>?
but having to specify the type every time is quite repetitive.
In my tests, using other types than Results<..>? kept the type inference intact. And this could be caused by having to specify the parent class in Self: Object (which is required because of Results).
Any help is appreciated.
This is a limitation of Swift's generics machinery. The compiler can generate a concrete signature for static func search(needle: String) -> Results<Object>? which satisfies the type constraint because Object subclasses will match this. You could probably file a bug towards bugs.swift.org because I think the Swift core team would also consider this to be a bug, if not very unexpected behavior.
However, you can modify your code to use protocol extensions to do what you want:
protocol SearchableRealmModel {}
extension SearchableRealmModel where Self: Object {
static func search(needle: String) -> Results<Self> {
return try! Realm().objects(Self).filter("name == '\(needle)'")
}
}
class Thing: Object, SearchableRealmModel {
dynamic var name = ""
}
let result = Thing.search("thing1") // => inferred as Results<Thing>
print(result.first?.name)
If you want custom implementations of search for other Realm models, you can reimplement the function there, which the compiler will prioritize over the protocol extension version:
class OtherThing: Object, SearchableRealmModel {
dynamic var id = ""
static func search(needle: String) -> Results<OtherThing> {
return try! Realm().objects(OtherThing).filter("id == '\(needle)'")
}
}
I've been dabbling with Swift recently, and I've hit a weird stumbling block with type constraints not operating as I would expect they should (in comparison to say, Scala).
protocol Foo {
typealias T: Hashable
func somethingWithT() -> T
}
struct Bar: Foo {
typealias T = Int
func somethingWithT() -> T { return 1 }
}
func baz() -> [Foo] {
var myBars = [Foo]()
myBars.append(Bar())
return myBars
}
This throws an error in XCode:
Any ideas what is going on here? I want T to be hashable for use as a key in a dictionary, but from what I've read Hashable has a reference to Self somewhere, so it can only be used as a generic constraint, thus removing my ability to just have a [Foo].
I want to have a list of things that do Foo, but at this rate it seems I'll either have to remove the Hashable constraint or make it less generic..
I've tried cleaning my project and re-making, but no dice :(
The problem here is that the type of T (of the protocol) has to be known at runtime. Due to the lack of generics with type aliases you can work around that by making an AnyFoo type (like in the standard library AnyGenerator and AnySequence):
protocol Foo {
typealias T: Hashable
func somethingWithT() -> T
}
struct AnyFoo<T: Hashable>: Foo {
let function: () -> T
init<F: Foo where F.T == T>(_ foo: F) {
// storing a reference to the function of the original type
function = foo.somethingWithT
}
func somethingWithT() -> T {
return function()
}
}
struct Bar: Foo {
typealias T = Int
func somethingWithT() -> T { return 1 }
}
// instead of returning [Foo] you can return [AnyFoo<Int>]
func baz() -> [AnyFoo<Int>] {
var myBars = [AnyFoo<Int>]()
// converting the type or Bar
myBars.append(AnyFoo(Bar()))
return myBars
}
This is not a generic function but you can convert any Foo type with the same T to the same AnyFoo type
myBars is an array of T: Foo. You could certainly pass in a Bar, because it satisfies the constraint on T. But so could another concrete type. Since T must be a concrete type, you can't arbitrarily put a Bar on there without guaranteeing that the only thing you'll put in there are Bars. And the only way to do that in Swift with this function signature would be to pass in a Bar.
I think maybe what you want is an array of protocol objects, not a generic array.