young
(rtSwift)
1
I said this on twitter to John Sundell, who I learn a whole lot from.
I said that because I thought that's what the language reference say about closure:
Global and nested functions, as introduced in Functions, are actually special cases of closures. Closures take one of three forms:
- Global functions are closures that have a name and don’t capture any values.
- Nested functions are closures that have a name and can capture values from their enclosing function.
- Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.
I'm confused because he said:
... you can pass any method as if it was a closure, but that doesn't make all methods closures. Think of it this way: you can convert any method into a closure, just like how you can convert any Substring into a String. Doesn't make them the same, just interoperable.
I don't know what to make of this: if you can pass any method as closure, then methods are closure.
Anyway, I need final judgement on this so I can unconfused myself.
1 Like
I think it's just that the terminology is fuzzy here. Formally, the "close" part of "closure" is that it closes over its environment, i.e. it captures variables that are in scope. But in practice we tend to use to it to refer to any kind of function being treated as a value (i.e. assigned to a variable or passed as an argument), regardless of whether it captures something.
I suppose you could also argue that methods are in fact closures formally because they capture the instance variables of self.
Practically speaking: you can pass any kind of function, including methods and closures, as an argument.
6 Likes
SDGGiesbrecht
(Jeremy David Giesbrecht)
3
I would say you are both right.
In the context of a running program, “methods”, “functions”, and “closures” are the same. They operate in the same way. They serve the same purpose. They are interchangeable. I suspect they are even indistinguishable (although I’m not an assembly expert). This is likely what the language reference had in mind.
In the context of a programmer writing a .swift file, “methods”, “functions”, and “closures” are all different. They are used in different ways. They serve different purposes. They are not interchangeable. They are easily distinguishable. This is likely what Sundell had in mind.
You could compare it to the difference or lack thereof between a fraction and a decimal.
Both fractions and decimals represent a rational numbers, so as far as the operations of the natural world are concerned, they are the same. The rain doesn’t care whether it is falling at 9.8 m/s/s or 9 8⁄10 m/s/s.
But when you use fractions or decimals in writing to communicate information, they have very different properties from one another. On one hand, it is far easier to compare decimals than fractions. (Which is greater, 5⁄7 or 8⁄11? 0.714 or 0.727?). On the other hand, 23⁄43 is precise, whereas writing it as a decimal inevitably incurs a loss of precision: 0.534 883... There are things fractions can do that decimals can’t, and vice versa. And so fractions and decimals can also be thought of as inherently different things which serve inherently different purposes.
3 Likes
young
(rtSwift)
5
I hope someone can explain the exact reason of this error. Maybe this is a bug?
All class/struct methods are closures, thus far, I have not seen any exception to this. If you can define it, you can use it as closure
SDGGiesbrecht
(Jeremy David Giesbrecht)
6
That has nothing to do with methods and everything to do with local classes:
func foo() {
let i = 123
func bar() -> Int {
return i
}
let barClosure: () -> Int = {
return i
}
class Klass {
// Error: Class declaration cannot close over value ‘i’ defined in outer scope
func baz() -> Int {
return i
}
// Error: Class declaration cannot close over value ‘i’ defined in outer scope
let bazClosure: () -> Int = {
return i
}
}
}
1 Like
It's a bug. Function-nested declarations aren't used nearly as much as others, so this kind of thing doesn't get much visibility.
Compiles:
let i = 123
class Klass {
func baz() -> Int { i }
}
Compiles:
enum Outer {
static let i = 123
struct Inner {
func baz() -> Int { i }
}
}
1 Like
Lantua
11
In case it got swept by the wayside. It is absolutely not a bug.
Class declarations cannot capture local contexts. They only capture global context as needed (instance methods technically also capture self). Allowing such capturing requires a lot more overhead than you'd think, and the resulting behaviour likely won't make sense.
Normally, you shouldn't need to capture variables from instance methods since you can simply store it in the object.
To note, this also applies to essentially everything, not just methods,
func foo() {
let i = 0
class A {
var a = { i } // error
var b = i // error
func c() {
i // error
}
}
}
1 Like
Lantua
13
We can probably define it like that. Though it doesn't help us much in presence of @convention(c) and the likes. We could say that they can neither capture nor reference, but then it's just another concept that one needs to learn.
Not that it's a bad thing. Implicit capturing is already different from explicit one (with capture list), but they both constitute the context that are captured by the closures.
The act of copying itself can be raced too. It's not a property of referencing per se. If anything, since the language has no concept of concurrency (until recently), I'd be surprised if we can speak about such things using pure in-language terminologies.
Jumhyn
(Frederick Kellison-Linn)
14
That's not generally how the term is used in Swift as I understand it. Capturing by default is by reference, and can be made to be by value by sticking the captured value in an explicit capture list ([foo]).
Also, this from above:
Is not quite accurate. Yes, each invocation of makeFunc creates a different copy of i, but it's not the case that this means that i gets copied by the capture (as is implied by saying it gets "captured" instead of "referenced"). E.g.,
func makeFuncs(_ i: Int) -> (increment: () -> Void, print: () -> Void) {
var i = i
return ({ i += 1 }, { print(i) })
}
let funcs = makeFuncs(0)
funcs.print() // 0
funcs.increment()
funcs.print() // 1
If we instead capture i in the second closure by value:
return ({ i += 1 }, { [i] in print(i) })
then the output becomes:
0
0
1 Like
Jumhyn
(Frederick Kellison-Linn)
16
Right, makes sense. I just wanted to point out that this is a property of how parameter declarations work in Swift versus global variables that is independent of the semantics of capturing.
1 Like