Ali
Ali

Reputation: 515

What is the point in declaring a variable of function type in Swift?

enter image description here

I got this from a book, this code was used in a didSet method and I'm confused on why this would be useful when you can just write a function that calls either function slowdown() or function speedup()?? This is basically declaring a "variable function" and "setting it equal to its own definition" which then returns a "function"? (correct me if I'm wrong). Why the need to use a variable and set it equal to the definition when I could just create a function? What is the usefulness in that?

Upvotes: 3

Views: 64

Answers (1)

Rob Napier
Rob Napier

Reputation: 299603

When you start to learn about function literals, functions that take and return other functions, and variables that hold functions, all kinds of new patterns open up. This is one of the most powerful tools in programming.

Your description isn't quite correct. It's not "setting it equal to its own definition." It's treating functions as values, just like integers, booleans, or structs. And when you treat a function as a value, you can combine functions, much like you combine other values. And that is incredibly powerful.

Let's consider just a very basic example based on yours:

var speed = 0
func speedup() { speed++ }
func slowdown() { speed-- }
var changeSpeed = speedup

speed // 0
changeSpeed()
speed // 1

OK great. But as you say, we could have set a bool or something and just done what we wanted to do. But let's take it further:

func twice(f: () -> Void) -> (() -> Void) { return { f(); f() } }

This is a function that takes a function and returns a new function that does the first one twice. Read that again, and think about it. It's a function that takes a function and doubles whatever that function does. We don't care what that function is. We can double anything.

So let's double our changing function:

changeSpeed = twice(changeSpeed)
changeSpeed()
speed // 3

We didn't have to modify speedup to handle this. twice doesn't care if we pass speedup or slowdown or some other function that we might invent in the future. We have a wide variety of ways we can expand this code without having to rewrite any of the working original code. And we have access to higher-order functions like twice. These are functions that take or return other functions, and they're incredibly powerful. In Swift, they include things like map and filter, and it's all tied to the ability to create literal functions and assign them to things.

Let's take this a step further. Don't worry terribly about all the ideas in this code, just on what it allows us to do.

import Darwin

var speed = 0
func speedup() { speed++ }
func slowdown() { speed-- }

var changeSpeed: () throws -> () = speedup

// This funny syntax lets us more easily create a function that takes a 
// function and retries it some number of times. It's called "currying".
// But the point is that calling `retry` will create a new function that
// retries the given function.
func retry(n: Int)(_ f: () throws -> Void)() rethrows {
    for _ in 1..<n {
        do {
            try f()
            return
        } catch {
            print("It failed. Let's try it again!")
        }
    }
    // One last try. If it fails, so we do we
    try f()
}

struct FailedError : ErrorType {}

// Similarly, `maybe` takes a function and might choose not to run it
func maybe(p: Float)(_ f: () throws -> ())() throws {
    if Float(arc4random())/Float(INT32_MAX) < p {
        try f()
    } else {
        throw FailedError()
    }
}

// With that, we can create a function called "lotsOfTries" that tries
// other functions 10 times. Yes, we're creating a function with `let`.
let lotsOfTries = retry(10)

// And we can create a function that fails other functions most of the time.
let badChance = maybe(0.15)

// And then we can glue them all together without knowing how they work internally.
changeSpeed = lotsOfTries(badChance(speedup))

do {
    try changeSpeed()
    print(speed)
} catch {
    print("Couldn't do it, sorry")
}

So those are the kinds of functions you can build once you really embrace this kind of coding. In my own code, I use functions similar to retry because "retry some number of times, but only for certain kinds of errors, and not too many times" is a really complicated problem independent of what's being retried. And I create functions that log calls to other functions, which is really convenient for consistent logging. And I have HTTP request handlers that perform authentication, and then wrap other HTTP request handlers that do proxying that wrap other HTTP request handlers that.... this is how functional composition works, and it can really change how you write programs.

And also, in the simplest of cases, this lets us do callbacks. So that comes up now and then…

Upvotes: 5

Related Questions