Any way to iterate a tuple in swift?
SwiftSwift Problem Overview
I am curious how to do a for loop with a tuple in swift.
I know that to access each member you can use dot notation using the index number
var tupleList = ("A",2.9,3,8,5,6,7,8,9)
for each in tupleList {
println(each)
}
//Error: Type does not conform to protocol sequence
Swift Solutions
Solution 1 - Swift
Yes, you can!
func iterate<C,R>(t:C, block:(String,Any)->R) {
let mirror = reflect(t)
for i in 0..<mirror.count {
block(mirror[i].0, mirror[i].1.value)
}
}
And voila!
let tuple = ((false, true), 42, 42.195, "42.195km")
iterate(tuple) { println("\($0) => \($1)") }
iterate(tuple.0){ println("\($0) => \($1)")}
iterate(tuple.0.0) { println("\($0) => \($1)")} // no-op
Note the last one is not a tuple so nothing happens (though it is a 1-tuple or "Single" which content can be accessed .0
, reflect(it).count
is 0).
What's interesting is that iterate()
can iterate even other types of collection.
iterate([0,1]) { println("\($0) => \($1)") }
iterate(["zero":0,"one":1]) { println("\($0) => \($1)") }
And that collection includes class
and struct
!
struct Point { var x = 0.0, y = 0.0 }
class Rect { var tl = Point(), br = Point() }
iterate(Point()) { println("\($0) => \($1)") }
iterate(Rect()) { println("\($0) => \($1)") }
Caveat: the value passed as the 2nd argument of the block is type Any
. You have to cast it back to the values with original type.
Solution 2 - Swift
You can using reflection Swift 5
Try this in a Playground:
let tuple = (1, 2, "3")
let tupleMirror = Mirror(reflecting: tuple)
let tupleElements = tupleMirror.children.map({ $0.value })
tupleElements
Output:
Solution 3 - Swift
Swift does not currently support iterating over tuples.
The biggest reasons are:
- There is no way at runtime to determine the number of elements in a tuple
- There is no way to access an element at a specific index except for the compile time accessors like
tupleList.0
. You would really want a subscripttupleList[0]
but that is not provided to us
Frankly, I can't see a reason that you would use a tuple instead of an Array if you want to iterate over it.
It doesn't make sense to iterate over a tuple because:
- Tuples always have a fixed length and each element has a fixed type
- You can name each tuple member with a name you can use to access it later
Arrays are well made to iterate over:
- Arbitrary length
- Can store multiple types using a common superclass or AnyObject
- Can be declared as a literal in a similar fashion to tuples:
var list = ["A",2.9,3,8,5,6,7,8,9]
Solution 4 - Swift
@dankogai's excellent solution, updated for Swift 3.0:
func iterate<Tuple>(_ tuple:Tuple, body:(_ label:String?,_ value:Any)->Void) {
for child in Mirror(reflecting: tuple).children {
body(child.label, child.value)
}
}
Usage remains identical to @dankogai's examples (beyond Swift 2's println()
→print()
rename).
Note that the label is now of type String?
when it was formerly String
, to match the type change from Swift 1's MirrorType.subscript(…).0
to Swift 3's Mirror.Child.label
. However, for labelless tuples the label
arg comes back as ".0"
, ".1"
, ".2"
, etc.— it's only nil
for some other types.
Also, I took the liberty of renaming types & args to better match Swift 3's solidified naming standards, and changing the closure return type to Void
.
Sidenote: I noticed somebody downvoted me here— I can't imagine why, other than the (fair) argument that building app functionality around reflection in Swift is hacking the type system, and is likely to lead to crappy code throughout (Swift's tuples shouldn't be considered an abstract data type, but rather a small collection of variables, akin to method args). As a counter-argument, I originally ended up porting this to Swift 3 in project because I needed it— for better description
s and debugDescription
s. Because sane debug output will saves you hours and hours of frustration. ;-) Additionally, this could be really useful for unit tests… because tests are ultimately most interested in “did the result of this operation match what we expect?”
Solution 5 - Swift
Details
- Xcode 11.2.1 (11B500), Swift 5.1
Base Solution
struct Tuple<T> {
let original: T
private let array: [Mirror.Child]
init(_ value: T) {
self.original = value
array = Array(Mirror(reflecting: original).children)
}
func forEach(closure: (Mirror.Child) -> Void) { array.forEach { closure($0) } }
func getOnlyValues<T: Any>() -> [T] { array.compactMap { $0.value as? T } }
func getAllValues() -> [Any] { array.compactMap { $0.value } }
}
Usage on base solution
let num: Int? = 3
let str: String? = nil
let x = (1, "stew", str, 5.4, 2, num)
let tuple = Tuple(x)
tuple.forEach { print("\($0)") }
print("\(tuple.getAllValues())") // [1, "stew", nil, 5.4, 2, Optional(3)]
print("\(tuple.getOnlyValues() as [Int])") // [1, 2, 3]
More sugar
func valuesFrom<V>(tuple: V) -> [Any] { return Tuple(tuple).getAllValues() }
func onlyValuesFrom<T,V>(tuple: V) -> [T] { return Tuple(tuple).getOnlyValues() as [T] }
print(valuesFrom(tuple: x)) // [1, "stew", nil, 5.4, 2, Optional(3)]
print(onlyValuesFrom(tuple: x) as [Int]) // [1, 2, 3]
Solution 6 - Swift
No, you can't. The reason is that tuple items are not all required to have the same type, so you would not be able to know what type each
should have.