Swiftwater Logo

Swift Functions

This entry is part 2 of 21 in the series Swiftwater

ABSTRACT

Swift allows the use of unnamed parameters (func B(_ C: Int) { - let A = B(C)), named parameters (func B(See: Int) { - func B(See C: Int) { - let A = B(See: C)), default values in parameters (func B(_ C: Int = 0) { - func B(See: Int = 0) { - func B(See C: Int = 0) { - let A = B()) and variadic parameters (func B(_ C: Int...) { - func C(1, 2, 3, 4))

Since there’s so much variety in the way these can be specified, I decided to see what this would mean, as far as implementation.

At the bottom of this post, I’ll attach the playground that I used to model this, but before that, I’ll discuss what I’ve discovered while learning this stuff.

First off, I have been reviewing Swift from “top to bottom.” I’m revisiting simple, basic stuff, because the way that I learn things (by actually “doing” stuff as soon as possible), will often leave “gaps.” Reviewing the language completely will allow me to fill these gaps in.

I’ve had a great deal of luck with Big Nerd Ranch. I took their Advanced iOS Bootcamp back in 2012 (It was Objective-C back then), and have used their books to learn new material since. I am currently reading their Swift Programming Guide.

This is the Apple documentation on Swift functions.

FUNCTION SIGNATURES

For the most part, Swift encourages you to force callers to use named parameters. This is good discipline. A little extra typing, but the function is a lot closer to “self-documenting”:

Standard Swift Parameters:

func swiftFunction(inParamA: Int, inParamB: Int, inParamC: Int, inParamD: Int, inParamE: Int) {
    print("inParamA: \(inParamA), inParamB: \(inParamB), inParamC: \(inParamC), inParamD: \(inParamD), inParamE: \(inParamE)")
}

This means that when we call the function, we MUST supply the parameter names:

    swiftFunction(inParamA: 0, inParamB: 1, inParamC: 2, inParamD: 3, inParamE: 4)

Standard Swift Parameters, with Argument Labels (Allows Different Names Inside and Outside the Function):

func swiftFunction(paramA inParamA: Int, paramB inParamB: Int, paramC inParamC: Int, paramD inParamD: Int, paramE inParamE: Int) {
    print("paramA: \(inParamA), paramB: \(inParamB), paramC: \(inParamC), paramD: \(inParamD), paramE: \(inParamE)")
}

When we call the function, we supply the “external” parameter names:

    swiftFunction(paramA: 0, paramB: 1, paramC: 2, paramD: 3, paramE: 4)

However, inside the function, we deal with the names of the parameters:

func swiftFunction(paramA inParamA: Int, paramB inParamB: Int, paramC inParamC: Int, paramD inParamD: Int, paramE inParamE: Int) {
    print("paramA: \(inParamA), paramB: \(inParamB), paramC: \(inParamC), paramD: \(inParamD), paramE: \(inParamE)")
}

Wildcard (Unnamed) Swift Parameters:

Swift also has the underscore character, designating the “wildcard” pattern, that can be used in a few places. We’ll look at how it appears in function declarations.

This is the way most languages work. Parameters are specified by position in the function signature and type.

func swiftFunction(_ inParamA: Int, _ inParamB: Int, _ inParamC: Int, _ inParamD: Int, _ inParamE: Int) {
    print("\(inParamA), \(inParamB), \(inParamC), \(inParamD), \(inParamE)")
}

When we call the function, we don’t use any parameter names:

    swiftFunction(0, 1, 2, 3, 4)

In fact, specifying parameter names will cause an error:

    //: This will cause an error. You can't use parameter names for wildcard parameters.
    swiftFunction(inParamA: 0, inParamB: 1, inParamC: 2, inParamD: 3, inParamE: 4)

You can also use wildcards to ignore parameters (which is sometimes necessary when satisfying closure/protocol requirements):

func swiftFunction(_ inParamA: Int, inParamB: Int, _: Int, _ inParamD: Int, _: Int, ignoreMe _: Int) {
    print("\(inParamA), \(inParamB), \(inParamD)")
}

You’ll still need to provide something to satisfy the call signature, but the value doesn’t matter, as it will be ignored.

    swiftFunction(0, inParamB: 1, 10000, 3, 20000, ignoreMe: 30000)

Function Signatures Varied by Return Type:

One of the cool things about Swift, is that you can vary function signatures by return type, not just the argument labels.

For example, let’s look at a series of functions that accept the same named Bool (the same parameter list), but can reply with different types:

func returnSomething(inParameter: Bool) -> Bool {
    return inParameter
}

func returnSomething(inParameter: Bool) -> Int {
    return inParameter ? 1 : 0
}

func returnSomething(inParameter: Bool) -> Double {
    return inParameter ? 1.0 : 0.0
}

func returnSomething(inParameter: Bool) -> String {
    return inParameter ? "OH, IT'S TWOO, IT'S TWOO, IT'S TWOO!" : "FALSE"
}

Note that the argument/parameter signature is exactly the same for all the functions, as is the function name. In many languages, this would be a compile-time error.

In Swift, it just means that you can’t ask the function to set the type for you, so you can’t use implicit type assignment.

In instances where there is only one function, you can use the function return to assign a type:

func returnSomething(inParameter: Bool) -> Bool {
    return inParameter
}

    let myGoalIsToBeABool = returnSomething(inParameter: true)

In the above case, where the Bool variant is the only declared function, you can implicitly assign the type.

However, as soon as we declare another return type, we lose the ability to implicitly assign:

func returnSomething(inParameter: Bool) -> Bool {
    return inParameter
}

func returnSomething(inParameter: Bool) -> Int {
    return inParameter ? 1 : 0
}

    // This will now cause an error.
    //let myGoalIsToBeABool = returnSomething(inParameter: true)
    // Now you need to explicitly require a return type:
    let myGoalIsToBeABool: Bool = returnSomething(inParameter: true)

In the sample playground, I show how this might be useful in a sequence type, but be careful. This could easily lead to confusion. It’s a bit of a weird pattern, and some folks may not be aware of it, so think of that when writing code that you don’t want to be saddled with for the rest of your life. I’ve learned (the hard way), that using “clever tricks” is one way to end up with unmaintainable code, and unmaintainable code is code that YOU own, because no one else will want to take care of it (or will apply bad fixes, because they don’t understand what you did -which means that YOU will be called back to exercise your toilet plunger and clean up the mess they made).

Mixing Parameter Names, Wildcards and Argument Labels

Now, we can “mix and match” parameter names, argument labels and wildcards:

func swiftFunction(inParamA: Int, _ inParamB: Int, paramC inParamC: Int, _ inParamD: Int, inParamE: Int) {
    print("paramA: \(inParamA), paramB: \(inParamB), paramC: \(inParamC), paramD: \(inParamD), paramE: \(inParamE)")
}
    swiftFunction(inParamA: 0, 1, paramC: 2, 3, inParamE: 4)

Note that the first and last arguments use the parameter names, the second from first, and second from last parameters use no argument names, and the middle parameter uses the argument label as its argument name.

Named Parameters and Function Types (Variables)

When we set a function type variable to a function, the parameter names/argument labels do not transfer. It is as if the function was all wildcards.

For example, let’s say that we have a named parameter function, like so:

func swiftFunctionWithNamedParameters(namedParam1: Int, paramArgumentName2 namedParam2: Int) {
    print("namedParam1: \(namedParam1), paramArgumentName2: \(namedParam2)")
}

Let’s set that into a variable, like so:

    let functionReference = swiftFunctionWithNamedParameters

At this point, we can call functionReference() with two Int arguments. However, let’s look at the difference between calling the main function directly, and the reference:

    swiftFunctionWithNamedParameters(namedParam1: 0, paramArgumentName2: 1)
    functionReference(0, 1)
    //: This will cause an error. Uncomment to see the error:
    //functionReference(namedParam1: 0, paramArgumentName2: 1)

As you can see, you are actually barred from using the argument labels. You can’t do this:

    //: This will cause an error. Uncomment to see the error:
    //let functionReference:(namedParam1: Int, paramArgumentName2: Int)->Void = swiftFunctionWithNamedParameters

The function reference behaves as if you had declared the function thusly:

func swiftFunctionWithNamedParameters(_ namedParam1: Int, _ namedParam2: Int) {
    print("\(namedParam1), \(namedParam2)")
}

Now, remember this behavior as we go into default argument values.

In the sample playground, I expand on this a bit, and show that function returns are not affected; only function parameters.

CLOSURES

Closures are basically the same as C/Objective-C blocks, or lambda functions in other languages.

It’s a way to specify a “throwaway” function that doesn’t necessarily have a name or a “home” in the current context (but it can have a name. Global functions are really just a specialized closure).

Here’s Apple’s take on closures. Things can get a bit whacky. Pay special attention to capture syntax, and take some time to understand capture lists.
I also address closures in another post in this series.

APPLYING DEFAULT VALUES TO PARAMETERS

Like most languages, Swift allows you to specify a default value for a function parameter. This is a value that is supplied to the function internals if nothing is supplied when the function is called.

func swiftFunctionWithADefaultParameter(_ namedParam1: Int, paramWithDefaultValue: Int = 5) {
    print("\(namedParam1), paramWithDefaultValue: \(paramWithDefaultValue)")
}

    //: This function can be called this way:
    swiftFunctionWithADefaultParameter(0, paramWithDefaultValue: 1)
    //: Or this way:
    swiftFunctionWithADefaultParameter(0)
    //: In which case, "5" will be used inside the function as the value for paramWithDefaultValue.

Again, as with most languages, you can have multiple parameters set with default values:

func swiftFunctionWithDefaultValues(param1: Int = 0, param2: Int = 1, param3: Int = 2) {
    print("param1: \(param1), param2: \(param2), param3: \(param3)")
}

    //: This function can be called this way:
    swiftFunctionWithDefaultValues(param1: 10, param2: 20, param3: 30)
    //: Or this way:
    swiftFunctionWithDefaultValues(param1: 10, param2: 20)
    //: In which case, "2" will be used inside the function as the value for param3.
    //: Or this way:
    swiftFunctionWithDefaultValues(param1: 10)
    //: In which case, "1" will be used inside the function as the value for param2 and "2" will be used inside the function as the value for param3.
    //: Or this way:
    swiftFunctionWithDefaultValues()
    //: In which case, "0" will be used inside the function as the value for param1, "1" will be used inside the function as the value for param2 and "2" will be used inside the function as the value for param3.

Now, because of the way that Swift works with named parameters, this actually gives you a somewhat unique ability. You can let previous parameters in the list default, while specifying values for later parameters. Let’s return to the function we just declared, but call it a bit differently:

    //: We will call the function like so:
    swiftFunctionWithDefaultValues(param3: 30)
    //: In which case, "0" will be used inside the function as the value for param1, "1" will be used inside the function as the value for param2, but "30" (supplied by the caller) will be used inside the function as the value for param3.
    //: We can also call the function like so:
    swiftFunctionWithDefaultValues(param2: 20)
    //: In which case, "0" will be used inside the function as the value for param1, "20" (Supplied by the caller) will be used inside the function as the value for param2 and "2" will be used inside the function as the value for param3.

In both the cases above, param1 had its parameter skipped in the call, so it used the default. Most languages don’t let you skip preceding default parameters.

If the parameters were not named parameters (using wildcards), then this would not be possible. Skipping early parameters requires that the first parameter specified in the function call be named (it does not have to have a default value).

Default values are not carried across to function types (variables).

Additionally, in cases where a function with default functions can “collide” with one that has fixed parameters, the fixed parameter function is always called. Ergo:

//: Let's define a function where all of its parameters have defaults.
func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int = 0, param2: Int = 0, param3: Int = 0) {
    print("param1: \(param1), param2: \(param2), param3: \(param3)")
}
//: This means that a valid call to this function would be swiftFunctionWithPotentiallyEmptyParameterList()
//: Now, let's define one with an ACTUAL empty parameter list:
func swiftFunctionWithPotentiallyEmptyParameterList() {
    print("BORK")
}
//: Swift allows you to define them, as they are different function signatures.
//: However, try calling it with an empty parameter list:
swiftFunctionWithPotentiallyEmptyParameterList()
//: Only the actual empty one gets called. There's no way to call the one with all defaults, unless you specify one of its parameters:
swiftFunctionWithPotentiallyEmptyParameterList(param1: 10)
//: Now, let's define a function where the first of its parameters has no default.
func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int, param2: Int = 0, param3: Int = 0) {
    print("param1: \(param1), param2: \(param2), param3: \(param3)")
}
//: You are allowed to define a function with a fixed single parameter with the same name as the one with a default.
//: This function will always be called if you specify a single parameter.
func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int) {
    print("This function only has param1: \(param1)")
}
//: You are NOT allowed to define one with the same signature, where the only differences are default values.
//: This will cause an error. Uncomment to see the error:
//func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int = 0, param2: Int, param3: Int) {
//    print("param1: \(param1), param2: \(param2), param3: \(param3)")
//}

Basically, watch out, here. You can make quite a mess without knowing it. Run the playground below, and look at the last set of outputs. Look at what gets printed. There’s likely a couple of surprises.

VARIADIC FUNCTION PARAMETERS

Variadic function parameters are ones that are “open ended.” They can have as many, or as few, values as you, the caller wants.

A fairly well-known variant of this, is the venerable sprintf() C function.

Basically, the function prototype for sprinf() is:

int sprintf ( char * str, const char * format, ... );

With the first char* providing a destination buffer for the parsing, the second, a format string, and the following parameters a variable-type and variable-length series of arguments that make sense when the format string is parsed.

Variadic parameters are indicated by a sequence of 3 dots (…).

Swift has a similar capability, but, instead of the variable-type sequence, each subsequent value is the same type, like an array (actually, exactly like an array).

So this means that declarations like “Int...” means “an Array of Int,” and “String...” means “an Array of String,” with the difference being that the values are loaded via the function call, instead of an Array variable.

You can also define multiple variadic parameters in a function signature:

func multipleVariadics(inIntegerArray: Int..., inStringArray: String...)

Here’s a couple of examples of simple variadic parameters:

//: This function will print whatever is passed in via the variadic parameter, which must all be Int values.
func variadicPrintSequenceOfIntegers(_ param0: Int...) { print("param0: \(param0)") }
variadicPrintSequenceOfIntegers(0)
variadicPrintSequenceOfIntegers(0,1)
variadicPrintSequenceOfIntegers(0,1,2)

In the above example, all the values are stuffed into the param0 parameter, which is presented as an Array of Int.

//: This function will print whatever is passed in via the variadic parameter, which must all be String values.
func variadicPrintSequenceOfIntegers(_ param0: String...) { print("param0: \(param0)") }
variadicPrintSequenceOfIntegers("0")
variadicPrintSequenceOfIntegers("0","1")
variadicPrintSequenceOfIntegers("0","1","2")

In most languages, the variadic parameter needs to be the last one in the list. This does not need to be the case in Swift; because of named parameters.

In Swift, you can specify MORE parameters after the variadic parameter, as long as you specify the name of the one immediately following the variadic parameter. You can’t use a wildcard immediately after a variadic:

//: This is an error.
func swiftTestVariadic(_ param0: Int, _ param1: String..., _ param2: String) {
    print("param0: \(param0), param1: \(param1), param2: \(param2)")
}
swiftTestVariadic(1, "Two","Three","Four","Five")

The above will not compile, because you can’t have a wildcard parameter directly after a variadic.

However, this will:

func swiftTestVariadic(_ param0: Int, _ param1: String..., param2: String) {
    print("param0: \(param0), param1: \(param1), param2: \(param2)")
}
swiftTestVariadic(1, "Two","Three","Four", param2: "Five")

That’s because the parameter has an explicit name.

You can have default values and wildcard parameters following variadic parameters, as long as there’s an explicitly-named parameter in between:

func swiftTestVariadic(_ param0: Int, _ param1: String..., param2: String = "Nope", _ param3: String = "Nothin' to see here, folks") {
    print("param0: \(param0), param1: \(param1), param2: \(param2)")
}
swiftTestVariadic(1, "Two","Three","Four")
swiftTestVariadic(1, "Two","Three","Four", param2: "Yep")
swiftTestVariadic(1, "Two","Three","Four", param2: "Yep", "There is something to see.")

Of course, variadic parameters can’t have default values.


SAMPLE PLAYGROUND

This playground illustrates and expands upon the issues discussed above.

/*:
 # FUNCTION TYPES AND PARAMETER NAMES
 
 [This is the Apple Page on Functions](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html)
 */

//: First, we define a function with explicitly named parameters.
func thisIsABasicFunction(with: Int, and: Int) {
    print("\(with), \(and)")
}

//: You call it in the approved fashion.
thisIsABasicFunction(with: 1, and: 2)

//: Now, assign it to a function reference.
let funcPtr1 = thisIsABasicFunction

//: This will not work (uncomment to see the error).
//funcPtr1(with: 3, and: 4)

//: You need to strip the parameter names when you call the reference.
funcPtr1(3,4)

//: This will not work, either (uncomment to see the error).
//let funcPtr2:(with: Int, and: Int)->Void = thisIsABasicFunction

//: Next, we return a named tuple.
func thisIsABasicFunctionThatReturnsATuple(with: Int, and: Int)->(with: Int, and: Int) {
    return (with: with, and: and)
}

//: Pretty straightforward. Does what it says on the tin.

let tupleoGold = thisIsABasicFunctionThatReturnsATuple(with:5, and: 6)
print("\(tupleoGold.with), \(tupleoGold.and)")

//: However, named tuple data members come across.

let funcPtr2 = thisIsABasicFunctionThatReturnsATuple
let tupleoGold2 = funcPtr2(7,8)
print("\(tupleoGold2.with), \(tupleoGold2.and)")

/*:
 # DEFAULT FUNCTION PARAMETERS
 */
//: In this first test, we leave most of the parameters explicitly named, but give one of them a default value:
func testFunc1(_ inFirstUnnamedParam :Int, inFirstNamedParam: Int, inNamedParamWithDefaultValue: Int = 0, inAnotherNamedParam: Int ) {
    print("inFirstUnnamedParam: \(inFirstUnnamedParam), inFirstNamedParam: \(inFirstNamedParam), inNamedParamWithDefaultValue: \(inNamedParamWithDefaultValue), inAnotherNamedParam: \(inAnotherNamedParam)")
}

print("Testing Set 1:")
//: First, we just call them normally, using explicit names for all:
testFunc1(10, inFirstNamedParam: 20, inNamedParamWithDefaultValue: 40, inAnotherNamedParam: 30)
//: Next, we leave out the one that has a default value:
testFunc1(10, inFirstNamedParam: 20, inAnotherNamedParam: 30)
//: This will cause an error (uncomment to see):
//testFunc1(10, inFirstNamedParam: 20, 40, inAnotherNamedParam: 30)
//: The lesson is that we can leave it out, but if we include it, we need to give it an explicit name

//: In this next one, we have two parameters that are not named, and have default values. The first parameter is unnamed, but does not have a default value, also, the parameter between the two named default parameters does not have a default value:
func testFunc2(_ inFirstUnnamedParam :Int, inFirstNamedParam: Int, _ inUnnamedParamWithDefaultValue: Int = 0, inAnotherNamedParam: Int, _ inAnotherUnnamedParamWithDefaultValue: Int = 5 ) {
    print("inFirstUnnamedParam: \(inFirstUnnamedParam), inFirstNamedParam: \(inFirstNamedParam), inNamedParamWithDefaultValue: \(inUnnamedParamWithDefaultValue), inAnotherNamedParam: \(inAnotherNamedParam), inAnotherUnnamedParamWithDefaultValue: \(inAnotherUnnamedParamWithDefaultValue)")
}

print("\nTesting Set 2:")
//: This will cause an error (uncomment to see the error):
//testFunc2(inFirstNamedParam: 10, inAnotherNamedParam: 30)
//: Note that we MUST supply a value for the first unnamed parameter, as well as the one that sits between the two unnamed parameters with default values:
testFunc2(10, inFirstNamedParam: 103, inAnotherNamedParam: 30)
//: Note that skipping the unnamed default parameters is OK:
testFunc2(10, inFirstNamedParam: 103, 20, inAnotherNamedParam: 30)
//: Note that the "in-between" named parameter allows us to specify a value for the last unnamed default parameter, without having to specify a value for the first unnamed default parameter:
testFunc2(10, inFirstNamedParam: 103, inAnotherNamedParam: 30, 60)
//: Everything specified:
testFunc2(10, inFirstNamedParam: 103, 20, inAnotherNamedParam: 30, 60)

//: This one is almost the same as the one above, but the first unnamed parameter has a default value, so it can be skipped:
func testFunc3(_ inFirstUnnamedParamWithDefaultValue: Int = 0, inFirstNamedParam: Int, _ inUnnamedParamWithDefaultValue: Int = 0, inAnotherNamedParam: Int, _ inAnotherUnnamedParamWithDefaultValue: Int = 5 ) {
    print("inFirstUnnamedParam: \(inFirstUnnamedParamWithDefaultValue), inFirstNamedParam: \(inFirstNamedParam), inNamedParamWithDefaultValue: \(inUnnamedParamWithDefaultValue), inAnotherNamedParam: \(inAnotherNamedParam), inAnotherUnnamedParamWithDefaultValue: \(inAnotherUnnamedParamWithDefaultValue)")
}

print("\nTesting Set 3:")
//: Minimal: We specify only the two parameters that have required names:
testFunc3(inFirstNamedParam: 10, inAnotherNamedParam: 20)
//: Notice that the first named parameter allows us to specify a default value for the SECOND unnamed default value parameter, allowing us to skip the first one:
testFunc3(inFirstNamedParam: 10, 20, inAnotherNamedParam: 30)
//: We can still specify the first default unnamed parameter:
testFunc3(100, inFirstNamedParam: 10, inAnotherNamedParam: 20, 60)
//: Everything specified:
testFunc3(100, inFirstNamedParam: 10, 20, inAnotherNamedParam: 30, 40)

//: OK. Now we get jiggy. We have a function with 5 unnamed and default parameters:
func testFunc4(_ param0: Int = 0, _ param1: Int = 1, _ param2: Int = 2, _ param3: Int = 3, _ param4: Int = 4) {
    print("param0: \(param0), param1: \(param1), param2: \(param2), param3: \(param3), param4: \(param4)")
}

print("\nTesting Set 4:")
//: Note that we can only cascade linearly through the list. You can't pick and choose which parameters to ignore and specify:
testFunc4()
testFunc4(10)
testFunc4(10,20)
testFunc4(10,20,30)
testFunc4(10,20,30,40)
testFunc4(10,20,30,40,50)

//: We stick a named default parameter in the middle of the list:
func testFunc5(_ param0: Int = 0, _ param1: Int = 1, namedParam2: Int = 2, _ param3: Int = 3, _ param4: Int = 4) {
    print("param0: \(param0), param1: \(param1), namedParam2: \(namedParam2), param3: \(param3), param4: \(param4)")
}

print("\nTesting Set 5:")
//: Since everything has a default, we can use the empty tuple argument:
testFunc5()
//: The inline named parameter will allow us to specify unnamed parameters AFTER the named parameter:
//: param0 and param1 are ignored:
testFunc5(namedParam2:64)
testFunc5(namedParam2:64,98)
testFunc5(namedParam2:64,98,34)
//: And we can specify before, but, like the first example, the values need to be linerarly specified:
testFunc5(76)
testFunc5(76,namedParam2:64)
testFunc5(76,namedParam2:64,98)
testFunc5(76,54,namedParam2:64,98)
testFunc5(76,54,namedParam2:64,98,34)

//: In our final example, we add another named default parameter inline, and that will allow us to have a bit more granularity in picking which unnamed ones we want to affect:
func testFunc6(_ param0: Int = 0, namedParam1: Int = 1, _ param2: Int = 2, namedParam3: Int = 3, _ param4: Int = 4) {
    print("param0: \(param0), namedParam1: \(namedParam1), param2: \(param2), namedParam3: \(namedParam3), param4: \(param4)")
}

print("\nTesting Set 6:")
testFunc6()
//: This affects ONLY the first unnamed default parameter (param0):
testFunc6(76)
//: This affects the first unnamed default parameter (param0), and THE UNNAMED PARAMETER AFTER THE FIRST NAMED ONE (param2). It ignores namedParam1, namedParam3 and param4:
testFunc6(76,64)
//: This sets all three of the UNNAMED parameters, but leaves out the named ones:
testFunc6(76,64,98)
//: This ignores param0, namedParam1, param2 and param4. It sets ONLY namedParam3:
testFunc6(namedParam3: 87)
//: This ignores namedParam1, param2 and param4:
testFunc6(76, namedParam3: 87)
//: This ignores param0, param2 and param4:
testFunc6(namedParam1: 65, namedParam3: 87)
//: This causes an error (Uncomment to see the error):
//testFunc6(76, namedParam3: 87, namedParam1: 65)
//: This ignores param0 and param4:
testFunc6(namedParam1: 65, 32, namedParam3: 87)
//: This ignores param0, namedParam1 and param2:
testFunc6(namedParam3: 87, 51)
//: This ignores namedParam1 and param2:
testFunc6(98, namedParam3: 87, 51)
//: This causes an error (uncomment to see the error):
//testFunc6(_, 98, namedParam3: 87, 51)
//: This causes an error (uncomment to see the error):
//testFunc6(nil, 98, namedParam3: 87, 51)

/*:
 # DEFAULT OPTIONALS:
 
 Note that with optional, a value of nil does NOT trigger the default value. It is a legitimate value. You actually have to have no value supplied:
 */

func functionWithOptionalGivenADefaultValue(_ optionalParam: Int? = -7) {
    if nil == optionalParam {
        print("Optional Value is nil")
    } else {
        if -7 == optionalParam! {
            print("Optional Value is the default")
        } else {
            print("Optional Value is: \(optionalParam!)")
        }
    }
}

//: We give an explicit positive 3 value.
functionWithOptionalGivenADefaultValue(3)
//: We give it no value at all, which should result in the default value of -7
functionWithOptionalGivenADefaultValue()
//: We give it a value of nil, which DOES NOT result in the default value.
functionWithOptionalGivenADefaultValue(nil)

func swiftFunctionWithAnOptionalAndANonOptional(param1: Int, param2: Int = 1, param3: Int) {
    print("param1: \(param1), param2: \(param2), param3: \(param3)")
}

//: We will see that optional parameter values do not propagate to function values.

let funcReference = swiftFunctionWithAnOptionalAndANonOptional

swiftFunctionWithAnOptionalAndANonOptional(param1: 10, param2: 20, param3: 30)
swiftFunctionWithAnOptionalAndANonOptional(param1: 10, param3: 30)

funcReference(10, 20, 30)
//: This is an error. Uncomment to see the error.
//funcReference(10, 30)

func swiftAnotherFunctionWithAnOptionalAndANonOptional(_ param1: Int, _ param2: Int = 1, _ param3: Int = 1) {
    print("param1: \(param1), param2: \(param2), param3: \(param3)")
}
swiftAnotherFunctionWithAnOptionalAndANonOptional(10, 20, 30)
swiftAnotherFunctionWithAnOptionalAndANonOptional(10, 20)
swiftAnotherFunctionWithAnOptionalAndANonOptional(10)

let funcReference2 = swiftAnotherFunctionWithAnOptionalAndANonOptional
funcReference2(10, 20, 30)
//: This is an error. Uncomment to see the error.
//funcReference2(10, 20)

//: ## FUNCTION SIGNATURE COLLISION
//: Let's define a function where all of its parameters have defaults.
func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int = 0, param2: Int = 0, param3: Int = 0) {
    print("param1: \(param1), param2: \(param2), param3: \(param3)")
}
//: This means that a valid call to this function would be swiftFunctionWithPotentiallyEmptyParameterList()
//: Now, let's define one with an ACTUAL empty parameter list:
func swiftFunctionWithPotentiallyEmptyParameterList() {
    print("BORK")
}
//: and one with a single fixed parameter:
func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int) {
    print("this is ONLY param1: \(param1)")
}
//: You are allowed to specify a function with a subset of the parameter signature:
func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int = 0, param2: Int = 0) {
    print("This does not have param3 -param1: \(param1), param2: \(param2)")
}
//: Notice where it gets called below. The smaller, more specific parameter list will always trump the more general one.
//: However, you are not allowed to define a function with the same signature, where the only difference is defaults.
//: This causes an error. Uncomment to see the error.
//func swiftFunctionWithPotentiallyEmptyParameterList(param1: Int, param2: Int = 0, param3: Int = 0) {
//    print("param1: \(param1), param2: \(param2), param3: \(param3)")
//}
//: Swift allows you to define them, as they are different function signatures.
//: However, try calling it with an empty parameter list:
swiftFunctionWithPotentiallyEmptyParameterList()
//: Only the actual empty one gets called. There's no way to call the one with all defaults, unless you specify one of its parameters:
swiftFunctionWithPotentiallyEmptyParameterList(param1: 10)
swiftFunctionWithPotentiallyEmptyParameterList(param1: 10, param2: 20)
swiftFunctionWithPotentiallyEmptyParameterList(param1: 10, param2: 20, param3: 30)
swiftFunctionWithPotentiallyEmptyParameterList(param2: 20)
swiftFunctionWithPotentiallyEmptyParameterList(param1: 10, param2: 20)
swiftFunctionWithPotentiallyEmptyParameterList(param2: 20, param3: 30)
swiftFunctionWithPotentiallyEmptyParameterList(param3: 30)
swiftFunctionWithPotentiallyEmptyParameterList(param1: 10, param3: 30)
swiftFunctionWithPotentiallyEmptyParameterList(param2: 20, param3: 30)
//: Now, let's define a function where the first of its parameters has no default.
func anotherSwiftFunctionWithManyDefaultsInParameterList(param1: Int, param2: Int = 0, param3: Int = 0) {
    print("param1: \(param1), param2: \(param2), param3: \(param3)")
}
//: You are allowed to define a function with a fixed single parameter with the same name as the one with a default:
func anotherSwiftFunctionWithManyDefaultsInParameterList(param1: Int) {
    print("only param1: \(param1)")
}

//: This will call the fixed parameter one
anotherSwiftFunctionWithManyDefaultsInParameterList(param1: 100)
//: This will call the one with all the defaults.
anotherSwiftFunctionWithManyDefaultsInParameterList(param1: 100, param2: 200)

/*:
 # FUNCTION SIGNATURES VARIED BY RETURN TYPE
 
 One of the really cool (and potentially dangerous) things about Swift, is that functions can be varied by *return type*; not just the parameter list.
 */

//: Here, we have a standard function that returns an Int:
func returnAnInt() -> Int {
    return 0
}

//: When we call it, we simply assign it, like so:
let thisWillBeAnInt = returnAnInt()

//: "thisWillBeAnInt" will be implicitly declared an Int.

//: However, we can also have the same function, with the same argument list, return different types:
func returnSomething(inParameter: Bool) -> Bool {
    return inParameter
}

func returnSomething(inParameter: Bool) -> Int {
    return inParameter ? 1 : 0
}

func returnSomething(inParameter: Bool) -> Double {
    return inParameter ? 1.0 : 0.0
}

func returnSomething(inParameter: Bool) -> String {
    return inParameter ? "OH, IT'S TWOO, IT'S TWOO, IT'S TWOO!" : "FALSE"
}

//: In a language like C, the above declarations would be illegal, as C uses the parameter list and function name to determine the signature.

//: This does have acaveat, though. You can no longer implicitly declare. You now need to explicitly declare the type expected when calling the function.

//: This will cause an error. Uncomment to see the error:
//let response = returnSomething(inParameter: true)

//: You need to specify each one:
let intResponse: Int = returnSomething(inParameter: true)
let boolResponse: Bool = returnSomething(inParameter: true)
let doubleResponse: Double = returnSomething(inParameter: true)
let stringResponse: String = returnSomething(inParameter: true)

//: Here's an example of how this could be useful.
//: I will declare a type that we'll pretend cleaves to [the Sequence Protocol](https://developer.apple.com/documentation/swift/sequence). It doesn't (because I'm lazy), but let's say that the subscript can be coerced:

class FauxSequence {
    typealias DataTuple = (string: String, int: Int, double: Double, bool: Bool)
    private var _internalData: [DataTuple]
    
    init(_ inDataTuples: [DataTuple]) {
        self._internalData = inDataTuples
    }
    
    //: Now, here's the beef. We have several types of subscripts:
    subscript(_ zeroBasedIndex: Int) -> String {
        let item = self._internalData[zeroBasedIndex]
        
        return item.string
    }
    
    subscript(_ zeroBasedIndex: Int) -> Int {
        let item = self._internalData[zeroBasedIndex]
        
        return item.int
    }
    
    subscript(_ zeroBasedIndex: Int) -> Double {
        let item = self._internalData[zeroBasedIndex]
        
        return item.double
    }
    
    subscript(_ zeroBasedIndex: Int) -> Bool {
        let item = self._internalData[zeroBasedIndex]
        
        return item.bool
    }
}

let fauxData: [FauxSequence.DataTuple] = [
    (string: "Zed", int: 0, double: 0.0, bool: false),
    (string: "Uno", int: 1, double: 1.0, bool: false),
    (string: "Two", int: 2, double: 2.0, bool: true),
    (string: "Twoo", int: 2, double: 2.0, bool: true)
]

let fauxSequence = FauxSequence(fauxData)

let twooString: String = fauxSequence[3]
let zedInt: Int = fauxSequence[0]
let twoDouble: Double = fauxSequence[2]
let wonBool: Bool = fauxSequence[1]

/*:
 # VARIADIC PARAMETERS
 */
func swiftTestVariadic0(_ param0: String...) {
    print("param0: \(param0)")
}

func swiftTestVariadic1(_ param0: Int, param1: String...) {
    print("param0: \(param0), param1: \(param1)")
}

func swiftTestVariadic2(_ param0: Int, param1: String..., param2: Int) {
    print("param0: \(param0), param1: \(param1), param2: \(param2)")
}

func swiftTestVariadic3(_ param0: Int, _ param1: String..., param2: Int) {
    print("param0: \(param0), param1: \(param1), param2: \(param2)")
}

swiftTestVariadic0("One")
swiftTestVariadic1(1, param1: "Two")
swiftTestVariadic1(1, param1: "Two","Three","Four")
swiftTestVariadic2(1, param1: "Two","Three","Four", param2: 5)
swiftTestVariadic3(1, "Two","Three","Four", param2: 5)

//: This is an error. Uncomment to see the error.
//func swiftTestVariadic4(_ param0: Int, _ param1: String..., _ param2: String) {
//    print("param0: \(param0), param1: \(param1), param2: \(param2)")
//}
//swiftTestVariadic4(1, "Two","Three","Four","Five")

func swiftTestVariadic5(_ param0: Int, _ param1: String..., param2: String, _ param3: String = "HELO") {
    print("param0: \(param0), param1: \(param1), param2: \(param2), param3: \(param3)")
}

swiftTestVariadic5(1, "Two","Three","Four", param2: "HI HOWAYA")
swiftTestVariadic5(1, "Two","Three","Four", param2: "HI HOWAYA", "HOW-DEE")

func swiftTestVariadic6(_ param0: Int, _ param1: String..., param2: String = "Nope", _ param3: String = "Nothin' to see here, folks") {
    print("param0: \(param0), param1: \(param1), param2: \(param2), param3: \(param3)")
}

swiftTestVariadic6(1, "Two","Three","Four")
swiftTestVariadic6(1, "Two","Three","Four", param2: "Yep")
swiftTestVariadic6(1, "Two","Three","Four", param2: "Yep", "There is something to see.")
//: This will "work," but it will give unexpected results:
swiftTestVariadic6(1, "Two","Three","Four", "There is something to see.")

//: This is an error. Uncomment to see the error.
//func swiftTestVariadic7(_ param0: Int = 1...) {
//    print("param0: \(param0)")
//}