Swift: Function with Default Parameter Before Non-Default Parameter

Swift: function with default parameter before non-default parameter

The current compiler does allow default parameters in the middle of a parameter list.

screenshot of Playground

You can call the function like this if you want to use the default value for the first parameter:

f(1)

If you want to supply a new value for the first parameter, use its external name:

f(first: 3, 1)

The documentation explains that parameters with a default value are automatically given an external name:

Swift provides an automatic external name for any defaulted parameter you define, if you do not provide an external name yourself. The automatic external name is the same as the local name, as if you had written a hash symbol before the local name in your code.

Default optional parameter in Swift function

Optionals and default parameters are two different things.

An Optional is a variable that can be nil, that's it.

Default parameters use a default value when you omit that parameter, this default value is specified like this: func test(param: Int = 0)

If you specify a parameter that is an optional, you have to provide it, even if the value you want to pass is nil. If your function looks like this func test(param: Int?), you can't call it like this test(). Even though the parameter is optional, it doesn't have a default value.

You can also combine the two and have a parameter that takes an optional where nil is the default value, like this: func test(param: Int? = nil).

Why aren't default parameters for functions respected when called via Selector in Swift?

Default parameters are inserted by the Swift compiler when you call the function.

So this is a compile-time thing.

When manually calling the function via a selector, you use the Objective-C runtime, which has no idea about your default parameters.

This is a runtime thing.

Moreover, default parameters don't exist in Objective-C.

You can think of Swift default parameters as a compile-time convenience.

But once you run your code, they're basically gone.

EDIT

If you're looking for a workaround, I can suggest having two different functions, without using default parameters:

@objc func printValue()
{
printValue(value: 7)
}

@objc func printValue(value:Int)
{}

This way, you can call it without arguments through the Objective-C runtime.
Note that when using #selector, you'll need to cast to resolve ambiguity:

self.button?.addTarget(self, action: #selector((printValue as () -> Void)), for: .touchUpInside)

When are Swift function default parameter values evaluated?

From The Swift Programming Language, under Language Reference > Declarations > Special Kinds of Parameters:

A parameter with an equals sign (=) and an expression after its type is understood to have a default value of the given expression. The given expression is evaluated when the function is called. If the parameter is omitted when calling the function, the default value is used instead.

You can demonstrate this for yourself by putting the following in a playground:

import Foundation

func foo(i: UInt32 = arc4random()) {
print(i)
}

foo()
foo()
foo()
foo()
foo()

which will print five different random numbers (unless the random number generator generates the same number five times by some astronomically improbable coincidence).

It's not explicit in the docs quoted above, so it's worth noting that when you do specify the argument when calling the function the default expression is not evaluated. You can demonstrate that in a playground too:

func getSomeInt() -> Int {
print("getSomeInt() was called")
return 42
}

func bar(_ i: Int = getSomeInt()) {
print(i)
}

bar(1)

and when that runs you'll see that "getSomeInt() was called" is not printed.

Using a property as a default parameter value for a method in the same class

I don't think you're doing anything wrong.

The language specification only says that a default parameter should come before non-default parameters (p169), and that the default value is defined by an expression (p637).

It does not say what that expression is allowed to reference. It seems like it is not allowed to reference the instance on which you are calling the method, i.e., self, which seems like it would be necessary to reference self.niceAnimal.

As a workaround, you could define the default parameter as an optional with a default value of nil, and then set the actual value with an "if let" that references the member variable in the default case, like so:

 class animal {
var niceAnimal: Bool
var numberOfLegs: Int

init(numberOfLegs: Int, animalIsNice: Bool) {
self.numberOfLegs = numberOfLegs
self.niceAnimal = animalIsNice
}

func description(numberOfLegs: Int, animalIsNice: Bool? = nil) {
if let animalIsNice = animalIsNice ?? self.niceAnimal {
// print
}
}
}

Implementing a function with a default parameter defined in a protocol

This is due to the fact that the call

castedCar.move(to: CGPoint(x: 20, y: 10))

is able to be resolved to the protocol requirement func move(to point: CGPoint) – therefore the call will be dynamically dispatched to via the protocol witness table (the mechanism by which protocol-typed values achieve polymorphism), allowing Car's implementation to be called.

However, the call

castedCar.move()

does not match the protocol requirement func move(to point: CGPoint). It therefore won't be dispatched to via the protocol witness table (which only contains method entries for protocol requirements). Instead, as castedCar is typed as Movable, the compiler will have to rely on static dispatch. Therefore the implementation in the protocol extension will be called.

Default parameter values are merely a static feature of functions – only a single overload of the function will actually be emitted by the compiler (one with all the parameters). Attempting to apply a function by excluding one of its parameters which has a default value will trigger the compiler to insert an evaluation of that default parameter value (as it may not be constant), and then insert that value at the call site.

For that reason, functions with default parameter values simply do not play well with dynamic dispatch. You can also get unexpected results with classes overriding methods with default parameter values – see for example this bug report.


One way to get the dynamic dispatch you want for the default parameter value would be to define a static property requirement in your protocol, along with a move() overload in a protocol extension which simply applies move(to:) with it.

protocol Moveable {
static var defaultMoveToPoint: CGPoint { get }
func move(to point: CGPoint)
}

extension Moveable {

static var defaultMoveToPoint: CGPoint {
return .zero
}

// Apply move(to:) with our given defined default. Because defaultMoveToPoint is a
// protocol requirement, it can be dynamically dispatched to.
func move() {
move(to: type(of: self).defaultMoveToPoint)
}

func move(to point: CGPoint) {
print("Moving to origin: \(point)")
}
}

class Car: Moveable {

static let defaultMoveToPoint = CGPoint(x: 1, y: 2)

func move(to point: CGPoint) {
print("Moving to point: \(point)")
}

}

let castedCar: Moveable = Car()
castedCar.move(to: CGPoint(x: 20, y: 10)) // Moving to point: (20.0, 10.0)
castedCar.move() // Moving to point: (1.0, 2.0)

Because defaultMoveToPoint is now a protocol requirement – it can be dynamically dispatched to, thus giving you your desired behaviour.

As an addendum, note that we're calling defaultMoveToPoint on type(of: self) rather than Self. This will give us the dynamic metatype value for the instance, rather than the static metatype value of what the method is called on, ensuring defaultMoveToPoint is dispatched correctly. If however, the static type of whatever move() is called on (with the exception of Moveable itself) is sufficient, you can use Self.

I go into the differences between the dynamic and static metatype values available in protocol extensions in more detail in this Q&A.

Is it possible to have parameters that aren't used as arguments?

You (everyone, really) would really benefit from reading the Swift book, cover to cover.

What you're looking for is called a default value.

func test(paramA: String = "Your default value here") {}

or

func test(paramA: String? = nil) {}

The former is simpler, but more limited. For example, you can't distinguish rather the default value "Your default value here" was used, or whether the caller passed in their own value, which happens to be "Your default value here"). In my experience, the distinction is seldom required, but it's good to call out just in case.

In the latter case, you have the flexibility to handle the optional in many more ways. You could substitute a default value with ??, do conditional binding, map it, etc.

Default Parameter Value for Function Type as Parameter Type in Swift 3

First of all, a question should provide a code block as specific as possible.

The question

So, you have a function foo which accepts a parameter of type closure and you want to provide a default value right?

The solution

Here's the code

func foo(completion: ()->() = { _ in print("Default completion") }) {
completion()
}

Testing

Now you can call foo passing your own closure

foo { print("Hello world") } // Hello world

Or using using the default param

foo() // Default completion

Using a class' method as default parameter in Swift

In

func operate(n1: Int, n2: Int, _ f: ((Int, Int) -> Int)? = add) -> Int 

add is evaluated as Calculator.add which has the type

 (Calculator) -> (Int, Int) -> Int

as explained in Instance Methods are “Curried” Functions in Swift.

One option is to use nil as the default value (since you do optional chaining anyway later):

class Calculator {

func operate(n1: Int, n2: Int, _ f: ((Int, Int) -> Int)? = nil) -> Int {
return (f ?? add)(n1, n2)
}

func add(n1: Int, n2: Int) -> Int {
return n1 + n2
}
}

Another option is to make the default function a static method, and the function parameter non-optional:

class Calculator {

func operate(n1: Int, n2: Int, _ f: ((Int, Int) -> Int) = add) -> Int {
return f(n1, n2)
}

static func add(n1: Int, n2: Int) -> Int {
return n1 + n2
}
}


Related Topics



Leave a reply



Submit