It seems absurd that this method signature does not compile in Swift 4:
class Bar<ValueType> {
func version() throws -> String where ValueType == [String: Any] { ... }
}
(Error: where clause cannot be attached to a non-generic declaration)
but this compiles fine:
class Bar<ValueType> {
func version<T>(_ foo: T? = nil) throws -> String where ValueType == [String: Any] { ... }
}
Anyone have insight as to why this is the case?
Because ValueType has nothing to do with this method (in the first example). It would be wrong to put such a method in a type (class/struct/enum), since it's not really a true member of that type. It's conditionally a member of that type, depending on the truth value of the where clause.
To achieve this, you would want to put this method in an extension of your type, with the where clause you want. E.g.
extension YourType where ValueType == [String: Any] {
func version() throws -> String { ... }
}
That has been finally allowed in Swift 5.3. See Contextual Where Clauses in the official Language Guide.
Citing from there:
You can write a generic where clause as part of a declaration that doesn’t have its own generic type constraints, when you’re already working in the context of generic types. For example, you can write a generic where clause on a subscript of a generic type or on a method in an extension to a generic type.
...
extension Container {
func average() -> Double where Item == Int {
var sum = 0.0
for index in 0..<count {
sum += Double(self[index])
}
return sum / Double(count)
}
That compiles just fine in Xcode 12 beta 4, but won't work in Xcode 11.6 that is shipped with Swift 5.2.4.
Related
I am using Xcode Version 11.3.1 (11C504)
I am trying to create a generic function in Swift that will reject its
parameter unless such a parameter is Optional.
In the following code, I was expecting the system to report errors in all calls to onlyCallableByAnOptable() made inside test(), because none of them provide an optional value as a parameter.
However, the system only reports non-protocol conformance if I remove the Optional extension that conforms to Optable!
Which to me, it means that the system is regarding any and all values as Optional, regardless!
Am I doing something wrong?
(By the way, the following code used to be working as expected in earlier versions of Swift. I just recently found out that it stopped working, for it was letting a non-Optional go through.)
protocol Optable {
func opt()
}
func onlyCallableByAnOptable<T>( _ value: T) -> T where T: Optable {
return value
}
// Comment the following line to get the errors
extension Optional: Optable { func opt() {} }
class TestOptable {
static func test()
{
let c = UIColor.blue
let s = "hi"
let i = Int(1)
if let o = onlyCallableByAnOptable(c) { print("color \(o)") }
//^ expected ERROR: Argument type 'UIColor' does not conform to expected type 'Optable'
if let o = onlyCallableByAnOptable(s) { print("string \(o)") }
//^ expected ERROR: Argument type 'String' does not conform to expected type 'Optable'
if let o = onlyCallableByAnOptable(i) { print("integer \(o)") }
//^ expected ERROR: Argument type 'Int' does not conform to expected type 'Optable'
}
}
Since you've made all Optionals conform to Optable and you are using the if let syntax to unwrap the result of the call to onlyCallableByAnOptable (which means the return type must be some kind of Optional, which means the parameter must also be that same type of Optional because both the parameter and the return type are of type T in your generic method), Swift is inferring the types being passed in as UIColor?, String?, and Int? (implicitly wrapping them in Optionals) instead of UIColor, String and Int.
I am the one who posted this question.
I was trying to create a generic function in Swift that would reject
its parameter unless such parameter is an Optional.
As #TylerTheCompiler pointed out, using my original implementation (in the question), Swift was inferring type T (used in onlyCallableByAnOptable()), based on the full context of the call, not solely on the type of the value provided as parameter to it, therefore inferring T to be an Optional.
For the sake of helping others who might be trying to achieve the same as I was, the following is my solution to the problem I had.
All calls to onlyCallableByAnOptable(...) now correctly yield errors due to non-protocol conformance.
Errors like: Argument type 'UIColor' does not conform to expected type 'Optable'
If anyone knows of a simpler solution, please do post it as an answer
to: How to create a generic function in Swift that will reject the given parameter unless it is an Optional?.
protocol Optable {
associatedtype OptableType
func optionalOptable() -> OptableType?
func opt()
}
func onlyCallableByAnOptable<T>( _ value: T) -> T.OptableType? where T: Optable {
return value.optionalOptable()
}
extension Optional: Optable {
typealias OptableType = Wrapped //: Wrapped is the type of the element, as defined in Optional
func opt() {}
func optionalOptable() -> OptableType? {
return self
}
}
class TestOptable {
static func test()
{
let c = UIColor.blue
let s = "hi"
let i = Int(1)
if let o = onlyCallableByAnOptable(c) { // ERROR, as was desired.
print("color \(o)")
}
if let o = onlyCallableByAnOptable(s) { // ERROR, as was desired.
print("string \(o)")
}
if let o = onlyCallableByAnOptable(i) { // ERROR, as was desired.
print("integer \(o)")
}
}
}
It is legal to say this (arr is an Array):
let arrenum = Array(arr.enumerated())
So why isn't it legal to say this?
extension Array {
func f() {
let arrenum = Array(self.enumerated())
// error: type of expression is ambiguous without more context
}
}
EDIT It seems this is a workaround:
extension Array {
func f() {
typealias Tup = (offset:Index, element:Element)
let arrenum = Array<Tup>(self.enumerated())
}
}
But why is that needed? (And is it right?)
This is a known bug (SR-1789). Swift currently has a feature where you can refer to a generic type within its own body without having to repeat its placeholder type(s) – the compiler will infer them for you to be the same as the type of self.
For example:
struct S<T> {
func foo(_ other: S) { // parameter inferred to be `S<T>`.
let x = S() // `x` inferred to be `S<T>`.
}
}
extension S {
func bar(_ other: S) {} // same in extensions too.
}
This is pretty convenient, but the bug you're running into is the fact that Swift will always make this inference, even if it's incorrect.
So, in your example:
extension Array {
func f() {
let arrenum = Array(self.enumerated())
// error: type of expression is ambiguous without more context
}
}
Swift interprets the code as let arrenum = Array<Element>(self.enumerated()), as you're in the body of Array<Element>. This is incorrect, because enumerated() yields a sequence of offset-element tuple pairs – Swift should have inferred Array to be Array<(offset: Int, element: Element)> instead.
One workaround, which you've already discovered, is to explicitly specify the placeholder type in order to prevent the compiler from making this incorrect inference.
extension Array {
func f() {
let arrenum = Array<(offset: Int, element: Element)>(self.enumerated())
}
}
Another possible workaround appears to be using the fully-qualified type, for example:
extension Array {
func f() {
let arrenum = Swift.Array(self.enumerated())
}
}
as it appears Swift doesn't do the same inference for fully-qualified types (I'm not sure if you should rely on this fact though).
Finally it's worth noting that instead of doing a call to Array's initialiser, you could use map(_:) instead to avoid the issue entirely:
extension Array {
func f() {
let arrenum = self.enumerated().map { $0 }
}
}
which, like the initialiser call, will give you back an array of offset-element pairs.
I am using Swift 3 with constrained generics (i.e. a where clause). I have a problem when I am trying to do generic type casting. Here is a simplified example of the problem:
func jsonToObj<T:DomainResource>(jsonStr: String) -> [T:DomainResource] {
let json = JSON(parseJSON: jsonStr).dictionaryObject
let bundle = SMART.Bundle(json: json)
let result = bundle.entry?.map() {
return $0.resource as! T
}
return result!
}
My problem is when I return from the method, the compiler complains its cannot convert type [T] to type [T:DomainResource]. If I remove the DomainResource constraint from the generic, it compiles and runs just fine.
That's not what I want, so, I tried this:
let result = bundle.entry?.map() {
return $0.resource as! T:DomainResource
}
Swift doesn't seem to know what that means. Any idea on how to work around this problem? I'd like to not just cast them all to DomainResource objects, if possible.
You wrote this function signature:
func jsonToObj<T:DomainResource>(jsonStr: String) -> [T:DomainResource]
This says that the jsonToObj(jsonStr:) method returns a dictionary whose keys are of type T and whose values are of type DomainResource. It looks like you just want to write this function signature:
func jsonToObj<T:DomainResource>(jsonStr: String) -> [T]
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
Note
Swift is changing rapidly, this question was asked regarding:
Xcode 7, Swift 2.0
Explanation
I'm looking to implement a generic return argument. Quite often, I find it necessary to implement an optional version overload so I can access the underlying type and handle it appropriately. Here's some manufactured functions. The assignment of String is just there as a placeholder for replication:
func ambiguous<T>() -> T {
let thing = "asdf"
return thing as! T
}
func ambiguous<T>() -> T? {
return nil
}
Now, if we look at the implementation:
// Fine
let a: String = ambiguous()
// Ambiguous
let b: String? = ambiguous()
This might seem obvious because you could assign type T to a variable of type T?. So it makes sense that it would have trouble inferring. The problem is, that with a type constraint, it suddenly works. (This can be anything, I'm using Equatable for easy replication.
func nonAmbiguous<T : Equatable>() -> T {
let thing: AnyObject = "asdf"
return thing as! T
}
func nonAmbiguous<T : Equatable>() -> T? {
return nil
}
And now, it functions as expected:
// Fine
let c: String = nonAmbiguous()
// Fine
let d: String? = nonAmbiguous()
Note, this also works with other type:
func nonAmbiguous<T>() -> [T] {
let thing: AnyObject = ["asdf"]
return thing as! [T]
}
func nonAmbiguous<T>() -> [T]? {
return nil
}
// Fine
let e: [String] = nonAmbiguous()
// Fine
let d: [String]? = nonAmbiguous()
Question:
Is there a way to have a return generic argument infer the appropriate overload through optionality?
if no
Is this a language feature, or a bug somewhere. If it's a language feature, please explain the underlying issue preventing the possibility of this behavior.
The first example is ambiguous because T can be inferred as both String
and String?.
The second example is not ambiguous because String is Equatable but String? is not, so T : Equatable cannot be inferred as String?.
The third case is not ambiguous because [T] is not
inferred as [String]?.
Remark: Generally, Optional<Wrapped> does not conform to Equatable
even if Wrapped does, in the same way as Array<Element>
does not conform to Equatable even if Element does.
This is a restriction of the current type system in Swift which
might be improved in a future version, compare
[swift-dev] RFC: Adding Optional variants of == for collections to the std lib.
from the Swift development mailing list.