Clearly, a constant (i.e. let) can have an assigned default parameter while a variable must use a computed value.
Why is that? The entire point of a default value is it shouldn't need to be specified or computed.
I understand map { Item... } but I'm not familiar with the map(Item...) construct used in Section A and I don't get how it works. Help?
Note: The Apple documentation for map() comes up well down on the Google results, after half a dozen StackOverflow etc pages. Probably because the link doesn't work and gets a 404.
So when you go to use the map function you can't specify a single value initializer because it doesn't exist. That is the kind that will work with the map(function) form of map.
When you make icon a computed property or constant, the compiler generated initializer then becomes
init(value: String) {
self.value = value
}
This works fine since there is a single argument.
You could work around the issue by writing your own initializer that looks like this:
init(value: String) {
self.value = value
self.icon = "πͺ"
}
Then the map(Item.init(value:)) will work because there is only one input parameter.
Another cool thing about map is that it works well with keypaths. So you can say, items.map(\.id) to get a list of value strings, for example.
So when you go to use the map function you can't specify a single value initializer because it doesn't exist. That is the kind that will work with the map(function) form of map.
But...but...why? It's an argument with a default! This works fine:
struct zot {
var x: Int
var y: String = "foo"
}
print("Value is: ", zot(x: 8))
// Output:
// Value is: zot(x: 8, y: "foo")
...wait, the above code is from a macOS CLI program while the original code is an iOS app. Is that the problem? tests No, it still chokes on the map(zot(x:)) version
Why??! What is happening?! Why can I use the single-arg version, except I can't if I'm calling it in a map, except I can if I'm using map {} instead of map()?
What is the difference between the two forms of map?
The difference is whether you're calling the function directly or referencing the 'unapplied' version of the function. For better or worse, Swift today only allows using default arguments when calling the function directly, though there's been discussions in the past about lifting that restriction.
One reason why it isn't totally trivial is that default arguments like #line inherit the context of the caller--that is in the function
func f(_ line: Int = #line) {
print(line)
}
the value of line will be populated with the line number of the calling function, so we'd have to decide how to handle the following case:
For the record, if #line is supposed to be based on the caller then in your example code I would expect it to refer to the g() line. Not sure why it would be anything else in that context. On the other hand, I would also expect #line to refer to the line where that token appears, not to the caller, so what do I know?
It's not really a limitation of the compiler: it's a deliberate design choice on the part of the language.
In Swift, a function f(x:y:) is not interchangeable with another function f(x:) irrespective of which or how many of the parameters have default arguments. When you call a function with such defaults, those default values are emitted on the caller sideβas though you (the caller) had written them out. It is important to understand these two interrelated points. Put concretely, given:
// Dynamic library A:
public func f(_: Int, _: Int, _: Int) { }
public func g(_ x: Int, _ y: Int, _ z: Int = 3) { f(x, y, z) }
public func h(_ x: Int, _ y: Int) { f(x, y, 3) }
// Your code:
g(1, 2)
h(1, 2)
...if a later version of dynamic library A changes g so that the default argument to z is 4, your app's existing compiled code exhibits no change in behavior; but if it changes the implementation of h so that it calls f(x, y, 4), your app will change behavior without recompilation.
Adding or removing default arguments is not tantamount to writing out additional overloads of a function with fewer parameters. Your question partly boils down to why you can't refer to g(x:y:z:) as g(x:y:) if z can be defaulted, and the answer is that those are not the same.
When you write map { Foo.init(value: $0) }, the implementation of map calls your closure that takes one argument, and your closure in turn calls Foo.init with two arguments (to both value and icon). When you write map(Foo.init(value:)), the implementation of map would need to call some Foo.init that takes one argument, and there is no such overload.
I think it's a fairly natural interpretation of Foo.init(value:) as sugar for precisely the partial application { Foo.init(value: $0) }. But this would have the potentially unexpected behavior of evaluating default arguments in the context of where the partial application was formed rather then where it ends up being called.