On Selecting Functions

Wow. I couldn’t think of a better title. The more specific version would have been “Simplifying The Selection Of Functions that Accept Identical Arguments”. And that was not… great…

Regardless of a terrible title, it is common situation to have a condition which requires the selection between two (or more) different functions to which the same arguments are being passed.

A typical example follows:

func firstFunc(with argument: Int) {
    print("first func \(argument)")
}

func secondFunc(with argument: Int) {
    print("second func \(argument*argument)")
}


let value = 43
if value > 30 {
    firstFunc(with: value)
} else {
    secondFunc(with: value)
}

This if-then construct always bothers me for two reasons.

First, I think it bugs me because it takes so much room vertically just to say “depending on the criteria, choose a function, then pass the value to it.” The value is the same in both cases, so it feels just a little bit duplicative to pass it along in two separate places in code. That is nominally a possible source of a bug.

Second, it also bothers me because all I am really doing is an assignment. I don’t want to do any other operations within the if-then blocks, but once it’s written, I can’t control what future programmers do and whether or not they see or respect my intent, possibly adding side effects. I would like an assignment structure that reflects that.

Lastly, I do realize that this is not the biggest issue in the world.

However, it did finally hit me that functions are 1st class citizens in swift and can be passed around just like any other parameter. Therefore we could choose a method like this based upon the criteria and then pass the argument to it directly:

let resolvedFunc = (value > 30) ? firstFunc(with:) : secondFunc(with:)
resolvedFunc(value)

That’s pretty cool. We went from 5 LOC to 2 and we removed any side effects. We could even get a little fancier and do it in one line, by wrapping the value in a single item array, and applying the selected function to it:

[value].forEach(value > 30 ? firstFunc : secondFunc)

While I love that it is just a single LOC, it is not an obvious pattern, so I would probably prefer the 2 line version. But just for kicks, we could also do it this way in a single LOC, though syntactically it’s even more confusing:

(value > 30 ? firstFunc(with:) : secondFunc(with:))(value)

Unsurprisingly, this approach works with multi-parameter functions as well:

func firstFunc(with argument: Int, other: Int) {
    print("first tup func \(argument) -- \(other)")
}

func secondFunc(with argument: Int, other: Int) {
    print("second tup func \(argument) -- \(other)")
}

let valuesTuple = (22, 43)

let resolvedFunc = (valuesTuple.0 < 30) ? firstFunc : secondFunc
resolvedTupFunc(tupVal.0, tupVal.1)

That’s nice, but the forEach approach is even cooler, and is why I assigned the two values to a tuple was that by applying the function to a single item array of tuples, it is not necessary to call out the individual arguments. They are implicitly applied as $0 and $1 in order:

[valuesTuple].forEach(valuesTuple.0 < 30 ? firstFunc : secondFunc)

I’m not sure if I’ll use this in production code with other people because I’m afraid that it could be a little difficult to grok at first or second glance, but I am glad to have finally found a clean way to do this that avoids the possibility of side effects.

It would be nice if swift had a function that was equivalent to map or forEach that would allow the application of a function or closure to a value.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s