Why to Use Tuples When We Can Use Array to Return Multiple Values in Swift

Why to use tuples when we can use array to return multiple values in swift

Tuples are anonymous structs that can be used in many ways, and one of them is to make returning multiple values from a function much easier.

The advantages of using a tuple instead of an array are:

  • multiple types can be stored in a tuple, whereas in an array you are restricted to one type only (unless you use [AnyObject])
  • fixed number of values: you cannot pass less or more parameters than expected, whereas in an array you can put any number of arguments
  • strongly typed: if parameters of different types are passed in the wrong positions, the compiler will detect that, whereas using an array that won't happen
  • refactoring: if the number of parameters, or their type, change, the compiler will produce a relevant compilation error, whereas with arrays that will pass unnoticed
  • named: it's possible to associate a name with each parameter
  • assignment is easier and more flexible - for example, the return value can be assigned to a tuple:

    let tuple = functionReturningTuple()

    or all parameters can be automatically extracted and assigned to variables

    let (param1, param2, param3) = functionReturningTuple()

    and it's possible to ignore some values

    let (param1, _, _) = functionReturningTuple()
  • similarity with function parameters: when a function is called, the parameters you pass are actually a tuple. Example:

    // SWIFT 2
    func doSomething(number: Int, text: String) {
    println("\(number): \(text)")
    }

    doSomething(1, "one")

    // SWIFT 3
    func doSomething(number: Int, text: String) {
    print("\(number): \(text)")
    }

    doSomething(number: 1, text: "one")

    (Deprecated in Swift 2) The function can also be invoked as:

    let params = (1, "one")
    doSomething(params)

This list is probably not exhaustive, but I think there's enough to make you favor tuples to arrays for returning multiple values

Why use a tuple rather than an array or dictionary in Swift?

A tuple can contain elements of different types. So for example you could declare a tuple containing a String and an Int, while all elements of an array have to be the same type, unless you use AnyObject and type casting.

Return multiple values from a function in swift

Return a tuple:

func getTime() -> (Int, Int, Int) {
...
return ( hour, minute, second)
}

Then it's invoked as:

let (hour, minute, second) = getTime()

or:

let time = getTime()
println("hour: \(time.0)")

Tuples vs NSDictionary when returning multiple value, seem same?

Each variable in Swift has a specific type, be it tuple or dictionary.

In the following tuple:

let nameAndAge = (name:"Jon", age:10)

nameAndAge is of type (String, Int)

In dictionary, the type is referred by the values it contains,

let dict = ["One": 1, 1: "One"]

dict is of type [AnyHashable:Any]

In

NSDictionary *dict= @{@"name":@"jon",@"age":[NSNumber numberWithLongLong: age],@"array":[NSArray new]} ... etc;

Although, it is in Objective-C, in Swift it will have a type of [String:Any]

i.e. let dict = ["name":"jon", "age":10, "array":[String]]

A dictionary key-value pair types are the one that satisfies all the elements it contains, i.e. top-most superclass.

How to return multiple values from an if-else or a switch case

As the OP asked for a Measurement solution, here we go. I borrowed Larme's combinedValue property.

var combinedValue : Double {
return Double(lengthIn1) + Double(lengthIn2)/100
}

Declare lengthUnit as a UnitLength value

let lengthUnit: UnitLength = .meters

The convert method is just one line

func convert(_ value: Double, from:  UnitLength, to:  UnitLength) -> Double {
return Measurement(value: value, unit: from).converted(to: to).value
}

And the three calculation properties are

var meterCalc: Double {
return convert(combinedValue, from: lengthUnit, to: .meters)
}

var inchCalc: Double {
return convert(combinedValue, from: lengthUnit, to: .inches)
}

var feetCalc: Double {
return convert(combinedValue, from: lengthUnit, to: .feet)
}

Can Swift func() return multiple values to an Objective-C caller?

Tuples (function that returns multiple value) are not supported in Objective-C but you can use block for that.

- (void)hilo:(int)holeSize prompt:(int)prompt callback:(void (^)(ballType : Int, ballColor : Int))result {
...
}

[self hilo:(int)holeSize prompt:(int)prompt callback:^(ballType : Int, ballColor : Int) {
....
}];

In Swift, is there a way to to assign elements of array to multiple variables?

In the list of patterns available, there is no "array pattern" or anything like that, so you can't pattern match on arrays. However, what you can do is:

let array = [1,2,3]
let (a, b, c) = (array[0], array[1], array[2])

This will throw an error at runtime if the array has less than 3 elements.

Can I Declare and initialize multiple variables in one line with a tuple?

func getImagesTuple(from array: Array<String>) -> (UIImage, UIImage, UIImage) {
(UIImage(named: array[0])!, UIImage(named: array[1])!, UIImage(named: array[2])!)
}

let (imageOne, imageTwo, imageThree) = getImagesTuple(from: ["imageOne", "imageTwo", "imageThree"])

Why Swift Tuples can be compared only when the number of elements is less than or equal to 6?

Background: Tuples aren't Equatable

Swift's tuples aren't Equatable, and they actually can't be (for now). It's impossible to write something like:

extension (T1, T2): Equatable { // Invalid
// ...
}

This is because Swift's tuples are structural types: Their identity is derived from their structure. Your (Int, String) is the same as my (Int, String).

You can contrast this from nominal types, whose identity is solely based off their name (well, and the name of the module that defines them), and whose structure is irrelevant. An enum E1 { case a, b } is different from an enum E2 { case a, b }, despite being structurally equivalent.

In Swift, only nominal types can conform to protocols (like Equatble), which precludes tuples from being able to participate.

...but == operators exist

Despite this, == operators for comparing tuples are provided by the standard library. (But remember, since there is still no conformance to Equatable, you can't pass a tuple to a function where an Equatable type is expected, e.g. func f<T: Equatable>(input: T).)

One == operator has to be manually be defined for every tuple arity, like:

public func == <A: Equatable, B: Equatable,                                                       >(lhs: (A,B        ), rhs: (A,B        )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, >(lhs: (A,B,C ), rhs: (A,B,C )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, >(lhs: (A,B,C,D ), rhs: (A,B,C,D )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, >(lhs: (A,B,C,D,E ), rhs: (A,B,C,D,E )) -> Bool { ... }
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool { ... }

Of course, this would be really tedious to write-out by hand. Instead, it's written using GYB ("Generate your Boilerplate"), a light-weight Python templating tool. It allows the library authors to implement == using just:

% for arity in range(2,7):
% typeParams = [chr(ord("A") + i) for i in range(arity)]
% tupleT = "({})".format(",".join(typeParams))
% equatableTypeParams = ", ".join(["{}: Equatable".format(c) for c in typeParams])

// ...

@inlinable // trivial-implementation
public func == <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
${", ".join("lhs.{}".format(i) for i in range(1, arity))}
) == (
${", ".join("rhs.{}".format(i) for i in range(1, arity))}
)
}

Which then gets expanded out by GYB to:

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable>(lhs: (A,B), rhs: (A,B)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1
) == (
rhs.1
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable>(lhs: (A,B,C), rhs: (A,B,C)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2
) == (
rhs.1, rhs.2
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable>(lhs: (A,B,C,D), rhs: (A,B,C,D)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3
) == (
rhs.1, rhs.2, rhs.3
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable>(lhs: (A,B,C,D,E), rhs: (A,B,C,D,E)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4
) == (
rhs.1, rhs.2, rhs.3, rhs.4
)
}

@inlinable // trivial-implementation
public func == <A: Equatable, B: Equatable, C: Equatable, D: Equatable, E: Equatable, F: Equatable>(lhs: (A,B,C,D,E,F), rhs: (A,B,C,D,E,F)) -> Bool {
guard lhs.0 == rhs.0 else { return false }
/*tail*/ return (
lhs.1, lhs.2, lhs.3, lhs.4, lhs.5
) == (
rhs.1, rhs.2, rhs.3, rhs.4, rhs.5
)
}

Even though they automated this boilerplate and could theoretically change for arity in range(2,7): to for arity in range(2,999):, there is still a cost: All of these implementations have to be compiled and produce machine code that ends up bloating the standard library. Thus, there's still a need for a cutoff. The library authors chose 6, though I don't know how they settled on that number in particular.

Future

There's two ways this might improve in the future:

  1. There is a Swift Evolution pitch (not yet implemented, so there's no official proposal yet) to introduce Variadic generics, which explicitly mentions this as one of the motivating examples:

    Finally, tuples have always held a special place in the Swift language, but working with arbitrary tuples remains a challenge today. In particular, there is no way to extend tuples, and so clients like the Swift Standard Library must take a similarly boilerplate-heavy approach and define special overloads at each arity for the comparison operators. There, the Standard Library chooses to artificially limit its overload set to tuples of length between 2 and 7, with each additional overload placing ever more strain on the type checker. Of particular note: This proposal lays the ground work for non-nominal conformances, but syntax for such conformances are out of scope.

    This proposed language feature would allow one to write:

    public func == <T...>(lhs: T..., rhs: T...) where T: Equatable -> Bool { 
    for (l, r) in zip(lhs, rhs) {
    guard l == r else { return false }
    }
    return true
    }

    Which would be a general-purpose == operator that can handle tuples or any arity.

  2. There is also interest in potentially supporting non-nominal conformances, allowing structural types like Tuples to conform to protocols (like Equatable).

    That would allow one to something like:

    extension<T...> (T...): Equatable where T: Equatable {
    public static func == (lhs: Self, rhs: Self) -> Bool {
    for (l, r) in zip(lhs, rhs) {
    guard l == r else { return false }
    }
    return true
    }
    }


Related Topics



Leave a reply



Submit