How do you provide a default argument for a generic function with type constraints? - swift

The following function definition is legal Swift:
func doSomething<T: StringProtocol>(value: T = "abc") {
// ...
}
The compiler is able to determine that the default argument "abc" is a String, and String conforms to StringProtocol.
This code however does not compile:
func doSomething<T: Collection>(value: T = "abc") where T.Element == Character {
// ...
}
The compiler error:
Default argument value of type 'String' cannot be converted to type 'T'
It seems as though the compiler would have just as much information as in the first case to determine that String is indeed convertible to T. Furthermore, if I remove the default argument and call the function with the same value, it works:
doSomething(value: "abc")
Can this function be written differently so that I can provide the default String argument? Is this a limitation of Swift, or simply a limitation of my mental model?

The significant constraint is T: ExpressibleByStringLiteral. That's what allows something to be initialized from a string literal.
func doSomething<T: Collection>(value: T = "abc")
where T.Element == Character, T: ExpressibleByStringLiteral {
// ...
}
As Leo Dabus notes, T.Element == Character is technically not necessary, but removing it changes the meaning. Just because something is a collection and can be initialized by a string literal does not mean that its elements are characters.
It's also worth noting that while all of this is possible, it generally is poor Swift IMO. Swift does not have any way to express what the default type is, so doSomething() in all of these cases causes "Generic parameter 'T' could not be inferred".
The correct solution IMO is an overload, which avoids all of these problems:
func doSomething<T: StringProtocol>(value: T) {
}
func doSomething() {
doSomething(value: "abc")
}
This allows you to make the default parameter not just "something that can be initialized with the literal "abc"", but what you really mean: the default value is the String "abc".
As a rule, default parameters are just conveniences for overloads, so you can generally replace any default parameter with an explicit overload that lacks that parameter.

Related

A question about parameter type and return type of buildBlock in #resultBuilder

According to the swift language guide, I need to implement buildBlock method when I define a result builder. And the guide also says:
"
static func buildBlock(_ components: Component...) -> Component
Combines an array of partial results into a single partial result. A result builder must implement this method.
"
Obviously, the parameter type and the return type should be the same. However, if I use different types, it also seems to be no problem. I write some simple code below:
#resultBuilder
struct DoubleBuilder {
static func buildBlock(_ components: Int) -> Double {
3
}
}
It compiles successfully in Xcode. Can the types be different?It seems like to be inconsistent with the official guide.
EDIT:
The parameter type of buildBlock method in ViewBuilder in SwiftUI is also different from the return type.
For example:
static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)>
When they say
static func buildBlock(_ components: Component...) -> Component
They don't strictly mean that there exists one type, Component, that buildBlock must take in and return. They just mean that buildBlock should take in a number of parameters, whose types are the types of "partial results", and return a type that is a type of a "partial result". Calling them both Component might be a bit confusing.
I think the Swift evolution proposal explains a little better (emphasis mine):
The typing here is subtle, as it often is in macro-like features. In the following descriptions, Expression stands for any type that is acceptable for an expression-statement to have (that is, a raw partial result), Component stands for any type that is acceptable for a partial or combined result to have, and FinalResult stands for any type that is acceptable to be ultimately returned by the transformed function.
So Component, Expression and FinalResult etc don't have to be fixed to a single type. You can even overload the buildXXX methods, as demonstrated later in the evolution proposal.
Ultimately, result builders undergo a transformation to calls to buildXXX, when you actually use them. That is when type errors, if you have them, will be reported.
For example,
#resultBuilder
struct MyBuilder {
static func buildBlock(_ p1: String, _ p2: String) -> Int {
1
}
}
func foo(#MyBuilder _ block: () -> Double) -> Double {
block()
}
foo { // Cannot convert value of type 'Int' to closure result type 'Double'
10 // Cannot convert value of type 'Int' to expected argument type 'String'
20 // Cannot convert value of type 'Int' to expected argument type 'String'
}
The first error is because the result builder's result type is Int, but foo's closure parameter expects a return value of type Double. The last two is because the transformation of the result builder attempts to call MyBuilder.buildBlock(10, 20).
It all makes sense if you look at the transformed closure:
foo {
let v1 = 10
let v2 = 20
return MyBuilder.buildBlock(v1, v2)
}
Essentially, as long as the transformed code compiles, there is no problem.
See more details about the transformation here.

Using type as a value, why is the "self" keyword required here?

I'm currently learning type as a value in functions and wrote this sample code to play around:
import Foundation
class Animal {
func sound() {
print("Generic animal noises")
}
}
func foo(_ t:Animal) {
print("Hi")
}
foo(Animal) //Cannot convert value of type 'Animal.Type' to expected argument type 'Animal'
I'm not surprised by this result. Obviously you cant pass the type itself as an argument where an instance of that type is expected. But notice that the compiler says that the argument I passed was of type Animal.Type. So if I did this, it should compile right?
func foo(_ t:Animal.Type) {
print("Hi")
}
foo(Animal) //Expected member name or constructor call after type name
This is what really confuses me a heck ton, the compiler told me it was of type Animal.Type *but after making this change it once again shows an error.
Of course I listened to the fix Swift suggests and do:
foo(Animal.self) //Works correctly
But my biggest question is: WHY? Isn't Animal itself the type? Why does the compiler require me to use Animal.self to get the type? This really confuses me, I would like for some guidance.
Self-answering, with help of comments, I was able to find out the reason:
Using .self after the type name is called Postfix Self Expression:
A postfix self expression consists of an expression or the name of a
type, immediately followed by .self. It has the following forms:
expression.self
type.self
The first form evaluates to the value of the expression. For example, x.self evaluates to x.
The second form evaluates to the value of the type. Use this form to access a type as a value. For example, because SomeClass.self evaluates to the SomeClass type itself, you can pass it to a function or method that accepts a type-level argument.
Thus, the .self keyword is required to consider the type as a value capable of being passed as an argument to functions.

Swift 4, Generics why do I need to cast to T here?

When running this code in Swift 4, The compiler throws the following error:
"Cannot convert return expression of type 'Comment?' to return type '_?', when running this code in a playground:
import UIKit
class Comment {}
class Other {}
func itemForSelectedTabIndex<T>(index: Int, type: T.Type) -> T? {
return (type is Comment.Type) ? getComment() : getOther()
}
func getComment() -> Comment {
return Comment()
}
func getOther() -> Other {
return Other()
}
let thing = itemForSelectedTabIndex(index: 0, type: Other.self)
In order to make this work, I need to cast the return value as generic, like this:
return (type is Comment.Type) ? getComment() as! T : getOther() as! T
Could someone explain the logic behind this?
If the expected return value is a 'Generic', and basically it won't matter what type I return, why the compiler complains about it? Shouldn't this work without casting?
Generics aren't some magical wildcards that can just have any value at any time.
When you callitemForSelectedTabIndex(index: 0, type: Comment.self), T is inferred to Comment. Likewise, for Other.
When T has been inferred to Comment, the same value of T is consistent throughout everywhere it's used. Thus, the return value must be of type Comment (or a sub-type).
Another is with your expression (type is Comment.Type) ? getComment() : getOther(). There are 2 cases, and neither of them are valid:
type is Comment.Type: getComment() returns a Comment, a type that is compatible with the value of T, which is Comment. But, the two operands of the conditional operator have no common supertype. That's not valid.
type is not Comment.Type: getOther() returns an Other, which may or may not be compatible with T. All we know about T is that it is not comment. This doesn't mean it's necessarily Other. It can be any other type, like Int. Thus, this return expression fails.
What you need is a common supertype of both types you wish to return. Most probably, a protocol is the correct choice (rather than a shared superclass):
protocol TabItem {}
class Comment {}
class Other {}
func itemForSelectedTabIndex<T: TabItem>(index: Int, type: T.Type) -> TabItem {
return getCommentOrOtherOrSomethingElse()
}

How flatMap API contract transforms Optional input to Non Optional result?

This is the contract of flatMap in Swift 3.0.2
public struct Array<Element> : RandomAccessCollection, MutableCollection {
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
}
If I take an Array of [String?] flatMap returns [String]
let albums = ["Fearless", nil, "Speak Now", nil, "Red"]
let result = albums.flatMap { $0 }
type(of: result)
// Array<String>.Type
Here ElementOfResult becomes String, why not String? ? How is the generic type system able to strip out the Optional part from the expression?
As you're using the identity transform { $0 }, the compiler will infer that ElementOfResult? (the result of the transform) is equivalent to Element (the argument of the transform). In this case, Element is String?, therefore ElementOfResult? == String?. There's no need for optional promotion here, so ElementOfResult can be inferred to be String.
Therefore flatMap(_:) in this case returns a [String].
Internally, this conversion from the closure's return of ElementOfResult? to ElementOfResult is simply done by conditionally unwrapping the optional, and if successful, the unwrapped value is appended to the result. You can see the exact implementation here.
As an addendum, note that as Martin points out, closure bodies only participate in type inference when they're single-statement closures (see this related bug report). The reasoning for this was given by Jordan Rose in this mailing list discussion:
Swift's type inference is currently statement-oriented, so there's no easy way to do [multiple-statement closure] inference. This is at least partly a compilation-time concern: Swift's type system allows many more possible conversions than, say, Haskell or OCaml, so solving the types for an entire multi-statement function is not a trivial problem, possibly not a tractable problem.
This means that for closures with multiple statements that are passed to methods such as map(_:) or flatMap(_:) (where the result type is a generic placeholder), you'll have to explicitly annotate the return type of the closure, or the method return itself.
For example, this doesn't compile:
// error: Unable to infer complex closure return type; add explicit type to disambiguate.
let result = albums.flatMap {
print($0 as Any)
return $0
}
But these do:
// explicitly annotate [ElementOfResult] to be [String] – thus ElementOfResult == String.
let result: [String] = albums.flatMap {
print($0 as Any)
return $0
}
// explicitly annotate ElementOfResult? to be String? – thus ElementOfResult == String.
let result = albums.flatMap { element -> String? in
print(element as Any)
return element
}

Array to String in Swift, stringInterpolationSegment

I am trying to convert an array of enums to a string in Swift. My enum is Printable and has a description property.
I thought this would work:
", ".join(a.map { String($0) })
but the compiler complains
Missing argument label 'stringInterpolationSegment:' in call
So, I follow the suggestion,
", ".join(a.map { String(stringInterpolationSegment: $0) })
But I do not understand:
Why is the argument label needed?
What is the type of stringInterpolationSegment?
You can't call a String initializer with your enum type because there isn't an initializer that takes that type.
There are a number of initializers for String that have the stringInterpolationSegment argument and they each implement it for a different type. The types include Bool, Float, Int, and Character among others. When all else fails, there is a generic fallback:
/// Create an instance containing `expr`\ 's `print` representation
init<T>(stringInterpolationSegment expr: T)
This is the version that is being called for your enum since it isn't one of the supported types.
Note, you can also do the following which is more succinct:
", ".join(a.map { toString($0) })
and you can skip the closure expression (thanks for pointing that out #Airspeed Velocity):
", ".join(a.map(toString))
As #vacawama points out, the error message is a bit of a red herring, and you can use map and toString to convert it.
But what’s nice is, if you’ve already implemented Printable, then the array’s implementation of Printable will also use it, so you can just do toString(a) to get a similar output.