1

There are multiple interesting questions here about attempting to define recursive closures in Swift. I think the one with the clearest answers is this post asking the question of why one can't declare and define a recursive closure in the same line in Swift. However, they don't ask the reverse question. That is, if I can't create code like this:

var countdown = { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}
// error: Variable used within its own initial value

Then why am I allowed to write code like this:

func countdown(_ i: Int) {
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}

What's so special about functions that makes them not have any issues when trying to call themselves within their own declaration? I understand the issue behind the closure: it's trying to capture a value (the closure itself) that doesn't yet exist. However, what I don't understand is why the function doesn't have the same problem. Looking at the Swift Book, we see that:

Global functions are closures that have a name and don’t capture any values.

Which is a half-answer: the function above doesn't cause issues because functions don't capture values. But then, how do functions do their work if they don't capture values (particularly recursive ones)?

NickZ
  • 101
  • 7

2 Answers2

3

Not quite sure what you're looking for by way of an answer. I mean, one simple answer is that this is how the Swift compiler works.

One reason could be that with a func expressions, e.g. func countdown(...) {}, the compiler can safely assume that the function will be defined by the time it invokes itself.

Whereas with variable definition (i.e. an expression where a variable acquires its value), it does not generally make such an assumption, because something like

var c = c + 1

clearly wouldn't work due to the same "Variable used within its own initial value" error.

That being said, the compiler probably could have special-cased the definition of closure-type variables since, unlike with non-closure variables, their actual value isn't needed at the time they are defined.

That's why a solution is to define the variable (or at least re-assure the compiler that it would be defined with !), so the compiler wouldn't complain:

var countdown: ((Int) -> Void)!
countdown = { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}
New Dev
  • 48,427
  • 12
  • 87
  • 129
  • Clever workaround but it's important to note that in this example, we have changed `countdown` from a stored property into a function (or closure), which is why the compiler doesn't complain. – trndjc Dec 08 '20 at 03:17
  • 2
    @bsod, What do you mean by stored property? I assumed this was a just a general statement, like `print(5)`; not a property (computed or stored) of an object. – New Dev Dec 08 '20 at 03:23
  • I just mean that in the OP’s example, the variable was (or was attempting to be) a stored property. And print is a function. – trndjc Dec 08 '20 at 04:00
  • 2
    And I mean that I understood `var countdown = {...}` in OP's example to mean a statement - not a property of an object @bsod – New Dev Dec 08 '20 at 04:03
  • Statement is not a type in Swift and countdown has to be of a certain type, which OP made one a property (stored) and the other a void-returning function. – trndjc Dec 08 '20 at 04:09
  • 1
    @bsod, maybe we aren't understanding each other. I meant this: `do { let fn = { $0 + 1 }; print(fn(5)) }`... here, `let fn = {...}` is an assignment statement where `fn` is a variable of type `(Int) -> Int` being assigned a value... same as with OP's example for `var countdown = {...}` – New Dev Dec 08 '20 at 04:16
  • 2
    You probably meant (assumed) that this was a `struct Foo { var countdown = {...} }` - possible, sure, but that's not how I understood it – New Dev Dec 08 '20 at 04:18
  • Right but your variable is of type function. Just because it’s a variable doesn’t mean it’s a property. The OP’s first example is a variable of (attempted) type property. – trndjc Dec 08 '20 at 04:50
  • This answer seems like it might be true: the swift compiler special-cases function declarations, and it should special-case closures but doesn't. I'll have to dive into the compiler code and see if I can find how functions are made a special case. I'll mark this as the right answer soon if no one comes along with that specific info. Thanks! – NickZ Dec 08 '20 at 11:26
  • @NickZ the Swift compiler doesn't special case functions, it just doesn't recognize your first attempt as a function. The syntax you chose in the first example will always be compiled as a stored property, which is why you can't access self in the getter. What this answer does is change `countdown` from a stored property into a function by changing syntax. Now I don't know if you meant to make `countdown` a stored property but you did and changing its type to function (through a change in syntax) is why this example works. But they are both functions and there is nothing special about it. – trndjc Dec 08 '20 at 16:17
  • @NickZ, i think my point was that it's not about closures vs functions. It's rather about one being a variable declaration `var countdown = ...`, and the other - a function declaration. Generally speaking, variable declarations/definitions cannot use themselves in the same statement. And function-type variables (i.e. `var increment = { p in ... }`) being assigned a closure aren't an exception a Swift compiler has made. – New Dev Dec 08 '20 at 18:27
  • @NewDev, right, thanks! I suppose my question rephrased is, "how does the compiler parse/handle functions differently than variables (including closures) so that this isn't an issue?" It must just be that it sees "func" and decides to not care about self-references inside. I guess that I personally would describe this as "special-casing" functions, considering that functions are [first class citizens](https://www.swiftbysundell.com/articles/first-class-functions-in-swift/) in swift, but maybe that's just semantics and isn't actually true at the compiler level. – NickZ Dec 09 '20 at 15:01
  • @NickZ, I don't know what internal representation the Swift compiler uses. While functions are definitely first class citizens, a function declaration is probably treated differently than a variable declaration (https://docs.swift.org/swift-book/ReferenceManual/Declarations.html) – New Dev Dec 09 '20 at 15:21
1

One is a function and the other is a stored property and stored properties (and properties) are states and functions are actions, semantically speaking. And this is how the Swift compiler interprets your code and why it doesn't work.

In your example, countdown is compiled as a stored property by the compiler. You can convert the stored property into a computed property to make it behave more like a function, but not only will you lose the ability to pass it arguments but the property would still not be able to access itself in its own getter (because it's still a property and not a function).

The answer by New Dev is a syntax workaround that changes countdown from a stored property into a function—he declared it as a function and later assigned it a closure. You tried to jam a function into a property and wondered why Swift gave special treatment to one over the other—it didn't, you just made them two different types. And I specifically point this out because your question (as it appears to me) is why functions are treated differently from properties. And it's because functions and properties are two inherently different things.

/* This syntax tells the compiler that
   countdown is a stored property, hence
   the error of accessing self in the
   property's own getter. */
var countdown = { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}

/* This syntax tells the compiler that
   countdown is a computed property, but
   this will still not compile because
   computed properties cannot take arguments
   or access self in the getter. */
var countdown: (Int) -> () { i in
    print(i)
    if i > 1 {
        countdown(i - 1)
    }
}

/* All this does is tell the compiler
   that countdown is of type function.
   This variable will no longer be compiled
   as a stored property. We've changed its
   type to function which is why it works. */
var countdown: ((Int) -> Void)!
trndjc
  • 11,654
  • 3
  • 38
  • 51