Yeah, I realized that wasn’t true after sending it—it could come from an
imported module, as long as it’s visible. Still, I imagine that retroactive
conformance wouldn’t be an issue because when the compiler type checks the
default at the declaration site, it would only see declarations that it
already knows about.
As for locality, yes, that’s a drawback of my approach, but I still would
prefer it to an odd-looking annotation based approach that introduced a
whole new and different syntax for certain kinds of default arguments.
···
On Sat, Nov 25, 2017 at 3:18 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Sat, Nov 25, 2017 at 4:44 PM, Tony Allevato <tony.allevato@gmail.com> > wrote:
On Sat, Nov 25, 2017 at 2:35 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Sat, Nov 25, 2017 at 4:25 PM, Tony Allevato <tony.allevato@gmail.com> >>> wrote:
On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@anandabits.com> >>>>> wrote:
On Nov 25, 2017, at 1:28 PM, Tony Allevato via swift-evolution < >>>>>> swift-evolution@swift.org> wrote:
On Fri, Nov 24, 2017 at 7:18 PM Xiaodi Wu via swift-evolution < >>>>>> swift-evolution@swift.org> wrote:
It's kludgy, but we could have something like:
@defaultArgument(configuration = (), where R.Configuration == Void) @defaultArgument(actionHandler = { _ in }, where R.Action == Never) func makeResource(with configuration: R.Configuration, actionHandler: @escaping (R.Action) -> Void) -> R { ... }
I don't like that we'd be setting a default argument on something
lexically before even encountering it in the declaration, but it's
serviceable.What if we could take advantage of the fact that you can have
non-constant expressions in default arguments? Overload resolution could
already do most of the job—what we need on top of that is a way for the
author to say that “if no overload matches, then it’s not an error—just
don’t have a default argument in that case”. Something like SFINAE in C++,
but more explicit.I’m imagining something like this:
func defaultConfiguration() -> Void {
return ()
}
func defaultActionHandler() -> (Never) -> Void {
return { _ in }
}
struct ResourceDescription<R: Resource> {
func makeResource(
with configuration: R.Configuration *=?* defaultConfiguration(),
actionHandler: @escaping (R.Action) -> Void *=?* defaultActionHandler()
) -> R {
// create a resource using the provided configuration
// connect the action handler
// return the resource
}
}The main difference here is the strawman =? syntax, which would
indicate that “the default argument exists if there is a way the RHS can be
satisfied for some instances of the generic arguments; otherwise, there is
no default”, instead of today’s behavior where it would be an error. There
could be multiple overloads of defaultConfiguration and
defaultActionHandler (even ones that are themselves generic) and it
would do the right thing when there are matches and when there aren’t.I like this approach because it mostly takes advantage of existing
language features and is fairly lightweight in terms of how it’s expressed
in code compared to regular default arguments—we’d just need to design the
new operator and type-checker logic around it.This is an interesting approach. One advantage to something in this
direction is that it could support defining different defaults for the same
argument under different constraints by overloading the default argument
factories on their return type.One concern I have is that it doesn’t allows us to clearly define
under which constraints a default argument is available. I suspect this
might be problematic especially for public interfaces where source
compatibility is a concern.It's certainly an interesting idea but it would suggest that the
constraints under which a default argument is available can change at
runtime. I'm concerned, like you, that this is difficult to reason about.
It is still unclear to me how widespread the underlying issue is that
requires conditional default arguments, but the conversation thus far has
been about compile-time constraints and Tony's design seems to envision
much more than that.This runtime/reasoning problem *already exists* today with default
arguments, because you can write something like this:struct Foo {
static var defaultExponent = 2.0func raise(_ x: Double, to exponent: Double = defaultExponent) {
print(pow(x, exponent))
}
}
Foo().raise(4) // "16.0"Foo.defaultExponent = 3.0Foo().raise(4) // "64.0"Swift lets you write a default value expression that references static
(but not instance) vars of the enclosing type, as well as anything else
that’s visible from that expression’s scope. Should people do this?
Probably not, for the reasons that you described.But the point is that my example is no more harmful or difficult to
reason about than default arguments in the language today. My proposed
solution *in no way* changes the runtime behavior of default argument
expressions. I’m not envisioning anything more than what default arguments
can already do except for adding a way to choose different default
factories (or choose none without error) based on the *static* types
of the generic arguments that are bound at a particular call site.Unless I misunderstand, with your example, a method would retroactively
gain a default argument if someone retroactively defines a function in an
extension. Is that not the case?Well, it's a pitch, not a complete design, so it's either possible or not
possible depending on what restrictions we place on it :)You're right that if this was implemented in a certain way, someone could
add overloads in other modules that would allow defaults to exist where
they otherwise wouldn't. If that's a concern, then the answer is
simple—have the compiler only look in the same module for matching
functions.That gets rid of retroactive conformance as a moving piece, but it still
disperses information about presence or absence of a default argument.
Today, that information is contained at the declaration of the function:
either there is `= default` or there isn't. Your design would still require
looking through an entire module (imagine, scrolling through all of
Foundation) to assure oneself that there is not some extension elsewhere
that impacts the number of default arguments.A function used in a default value expression today must be present in
the same module or file by virtue of the fact that if it wasn't, the
compiler wouldn't be able to reference it,Not at all:
// File A: struct T { func f(_ i: Int = T.i()) { print(i) } } T().f() // File B: extension T { static func i() -> Int { return 42 } }
It doesn't even have to be in the same module.
so this would be somewhat consistent with that behavior.
I think I prefer Xiaodi’s suggestion for that reason. His approach
could also support multiple defaults for the same parameter as long as the
constraints are not allowed to overlap (overlapping constraints would
result in ambiguity similar to ambiguous overload resolution) or an
explicit argument is required if they do.On Fri, Nov 24, 2017 at 8:36 PM, T.J. Usiyan via swift-evolution < >>>>>>> swift-evolution@swift.org> wrote:
I am all for this. are many types where there is an obvious 'zero'
or 'default' value and the ability to express "use that when possible"
without an overload is welcome.The best thing that I can think of right now, in terms of syntax,
is actually using @overloadstruct ResourceDescription<R: Resource> { func makeResource(with configuration: R.Configuration, actionHandler: @escaping (R.Action) -> Void) -> R @overload(R.Configuration == Void) func makeResource(actionHandler: @escaping (R.Action) -> Void) -> R @overload(R.Action == Never) func makeResource(with configuration: R.Configuration) -> R { // create a resource using the provided configuration // connect the action handler // return the resource } }
This isn't great though…
On Fri, Nov 24, 2017 at 6:11 PM, Matthew Johnson via >>>>>>>> swift-evolution <swift-evolution@swift.org> wrote:
As mentioned in my prior message, I currently have a PR open to
update the generics manifesto (
https://github.com/apple/swift/pull/13012\). I removed one topic
from that update at Doug Gregor’s request that it be discussed on the list
first.The idea is to add the ability to make default arguments
conditional (i.e. depend on generic constraints). It is currently possible
to emulate conditional default arguments using an overload set. This is
verbose, especially when several arguments are involved. Here is an
example use case using the overload method to emulate this feature:protocol Resource { associatedtype Configuration associatedtype Action } struct ResourceDescription<R: Resource> { func makeResource(with configuration: R.Configuration, actionHandler: @escaping (R.Action) -> Void) -> R { // create a resource using the provided configuration // connect the action handler // return the resource } } extension ResourceDescription where R.Configuration == Void { func makeResource(actionHandler: @escaping (R.Action) -> Void) -> R { return makeResource(with: (), actionHandler: actionHandler) } } extension ResourceDescription where R.Action == Never { func makeResource(with configuration: R.Configuration) -> R { return makeResource(with: configuration, actionHandler: { _ in }) } } extension ResourceDescription where R.Configuration == Void, R.Action == Never { func makeResource() -> R { return makeResource(with: (), actionHandler: { _ in }) } }
Adding language support for defining these more directly would
eliminate a lot of boilerplate and reduce the need for overloads. Doug
mentioned that it may also help simplify associated type inference (
Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).The reason that I call this a pre-pitch and one reason Doug
requested it be discussed on list is that I haven’t thought of a good way
to express this syntactically. I am interested in hearing general feedback
on the idea. I am also looking for syntax suggestions.Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution