Reputation: 6175
I keep seeing Swift classes where two methods are defined that only differ in return type. I'm not used to working in languages where this is allowed (Java, C#, etc), so I went looking for the documentation that describes how this works in Swift. I've found absolutely nothing anywhere. I would have expected an entire section on it in the Swift book. Where is this documented?
Here is an example of what I'm talking about (I'm using Swift 2, FWIW):
class MyClass {
subscript(key: Int) -> Int {
return 1
}
subscript(key: Int) -> String {
return "hi"
}
func getSomething() -> Int {
return 2
}
func getSomething() -> String {
return "hey"
}
}
Test:
let obj = MyClass()
//let x = obj[99]
// Doesn't compile: "Multiple candidates fail to match based on result type"
let result1: String = obj[123]
print("result1 \(result1)") // prints "result1 hi"
let result2: Int = obj[123]
print("result2 \(result2)") // prints "result2 1"
//let x = obj.getSomething()
// Doesn't compile: "Ambiguous use of 'getSomething'"
let result3: String = obj.getSomething()
print("result3 \(result3)") // prints "result3 hey"
let result4: Int = obj.getSomething()
print("result4 \(result4)") // prints "result4 2"
Upvotes: 10
Views: 6895
Reputation: 5332
This is a fairly cool aspect of Swift. I am currently using it in a generic class to have multiple subscripts. Here's a playground that I created to workshop it:
import Foundation
/*
Return Type Differentiation
This playground illustrates a rather useful capability of Swift: The ability to differentiate methods by return type; not just argument list.
In this example, we will set up multiple subscript() methods for an aggregator/façade class that will access the contained instances in
various ways, depending on the return type requested.
*/
// This class should win the igNoble prize for poitry.
struct A {
let poem: [String] = ["I'm a little teapot",
"bloody and cut.",
"This is my handle.",
"This is my "]
let multiplier: UInt32 = arc4random_uniform(100) // Just a random integer from 0 to 100.
}
// This class has a few different data types that are set at instantiation time, and one static instance of A
class B {
let stringProperty: String
let intProperty: Int = Int(arc4random_uniform(10))
let objectProperty: A = A()
init(_ string: String) {
self.stringProperty = string
}
// This will be used to demonstrate that we don't need to explicitly cast, if we only have one subscript method.
subscript(_ ignoredIndex: Int) -> A {
return self.objectProperty
}
}
// This class acts as a façade class. It provides an interface to its contained classes as if they were direct subscripts.
class C : Sequence {
let aArray: [B]
init() {
self.aArray = [B("hut"),B("butt")]
}
// You can have multiple subscript() methods, differentiated by return type.
subscript(_ index: Int) -> B {
return self.aArray[index]
}
subscript(_ index: Int) -> String {
return self.aArray[index].stringProperty
}
subscript(_ index: Int) -> UInt32 {
return (self[index] as A).multiplier
}
subscript(_ index: Int) -> Int {
return self.aArray[index].intProperty
}
subscript(_ index: Int) -> A {
return self.aArray[index].objectProperty
}
// These are not simple data return subscripts. In fact, there are no Float properties, so that one is made from whole cloth.
subscript(_ index: Int) -> Float {
return Float(self.aArray[index].intProperty) * Float((self[index] as A).multiplier)
}
subscript(_ index: Int) -> [String] {
var ret: [String] = []
let aInstance: B = self.aArray[index]
ret = aInstance[0].poem // No need for explicit casting if we only have one subscript.
ret[3] += self[index] + "." // This is allowed, as we know we're a String.
return ret
}
// You can only have one makeIterator() method.
func makeIterator() -> AnyIterator<[String]> {
var nextIndex = 0
// Return a "bottom-up" iterator for the list.
return AnyIterator() {
if nextIndex == self.aArray.count {
return nil
}
let ret: [String]! = self.aArray[nextIndex - 1].objectProperty.poem
nextIndex += 1
return ret
}
}
// You can have multiple methods with the same input signature, differentiated only by their output signature.
func returnIndexedElement(_ atIndex: Int) -> Int {
return self[atIndex] // Note no explicit casting is necessary, here.
}
func returnIndexedElement(_ atIndex: Int) -> UInt32 {
return self[atIndex]
}
func returnIndexedElement(_ atIndex: Int) -> A {
return self[atIndex]
}
func returnIndexedElement(_ atIndex: Int) -> B {
return self[atIndex]
}
func returnIndexedElement(_ atIndex: Int) -> Float {
return self[atIndex]
}
func returnIndexedElement(_ atIndex: Int) -> String {
return self[atIndex]
}
func returnIndexedElement(_ atIndex: Int) -> [String] {
return self[atIndex]
}
}
let mainObject = C()
// First, let's test the subscripts.
// We have 3 elements, so
let aObject1: A = mainObject[0]
let aObject2: B = mainObject[0]
let aString: String = mainObject[0]
let aPoem: [String] = mainObject[0]
let aInt: Int = mainObject[0]
let aUInt32 = mainObject[0] as UInt32
let aFloat = mainObject[0] as Float
// This will not work. You need to specify the type explicitly when using multiple subscripts, differentiated only by return type.
// let failObject = mainObject[0]
// However, this will work, because the class has only one subscript method defined.
let aObject2_Subscript = aObject2[0]
let aObject2_Poem = aObject2_Subscript.poem
// Next, test the accessor methods.
let bObject1: A = mainObject.returnIndexedElement(1)
let bObject2: B = mainObject.returnIndexedElement(1)
let bString: String = mainObject.returnIndexedElement(1)
let bPoem: [String] = mainObject.returnIndexedElement(1)
let bInt: Int = mainObject.returnIndexedElement(1)
let bUInt32 = mainObject.returnIndexedElement(1) as UInt32
let bFloat = mainObject.returnIndexedElement(1) as Float
// This will not work. You need to specify the type explicitly when using multiple methods, differentiated only by return type.
// let failObject = mainObject.returnIndexedElement(1)
Upvotes: 0
Reputation: 51911
Where is this documented?
As for subscript
:
Language Reference / Declarations / Subscript Declaration
You can overload a subscript declaration in the type in which it is declared, as long as the parameters or the return type differ from the one you’re overloading.
Language Guide / Subscripts / Subscript Options
A class or structure can provide as many subscript implementations as it needs, and the appropriate subscript to be used will be inferred based on the types of the value or values that are contained within the subscript braces at the point that the subscript is used.
I cannot find any official docs about overloading methods or functions. but in the Swift Blog:
Redefining Everything with the Swift REPL / Redefinition or Overload?
Keep in mind that Swift allows function overloading even when two signatures differ only in their return type.
Upvotes: 10
Reputation: 2843
A function's type is determined by the type of its arguments and the type of its return value, and the compiler can disambiguate similarly named functions by their type - from your example:
subscript(key: Int) -> Int {
return 1
}
...has type (Int) -> Int
subscript(key: Int) -> String {
return "hi"
}
...has type (Int) -> String
-- so though they are similarly named, the compiler can infer which one is being called by how the return value is being assigned (or since this is a subscript
, by what value is being assigned to that subscript)
continuing:
func getSomething() -> Int {
return 2
}
...has type () -> Int
func getSomething() -> String {
return "hey"
}
...has type () -> String
note: where you could get yourself into trouble is if you don't provide the compiler enough information for it to deduce which function you are calling, e.g. if you simply called getSomething()
without doing anything with its return value, it would complain about ambiguous use of 'getSomething'
EDIT - ah, I see in your sample code now that you do in fact provide an example where this is the case :) by assigning the return value to a constant for which you have not specified the type (let x = getSomething()
) there is not enough information for the compiler to sort out which function you are calling
EDIT EDIT - note that where I begin by saying 'the compiler can disambiguate similarly named functions by their type', function names are determined by: (1) the identifier for the function, along with (2) the identifiers for the function's external parameter names - so e.g. though the following two function both have the same type and function identifier, they are different functions and have different function names because they differ in the identifiers used for their external parameter names:
func getSomething(thing: String, howMany: Int) -> String
...has type (String, Int) -> String
, and is named getSomething(_:howMany:)
func getSomething(thing: String, howManyTimes: Int) -> String
...has type (String, Int) -> String
, and is named getSomething(_:howManyTimes:)
Upvotes: 6