> On Nov 27, 2017, at 4:55 PM, Tony Allevato <tony.allev...@gmail.com> wrote: > > > > On Mon, Nov 27, 2017 at 2:39 PM Matthew Johnson <matt...@anandabits.com > <mailto:matt...@anandabits.com>> wrote: >> On Nov 27, 2017, at 4:25 PM, Tony Allevato <tony.allev...@gmail.com >> <mailto:tony.allev...@gmail.com>> wrote: >> >> >> >> On Mon, Nov 27, 2017 at 2:19 PM Matthew Johnson <matt...@anandabits.com >> <mailto:matt...@anandabits.com>> wrote: >>> On Nov 27, 2017, at 3:56 PM, Howard Lovatt via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>> >>> Really like Tony’s suggestion, much cleaner than yet another annotation >>> rammed into the signature. Also the idea of a static factory that could >>> accept previously initialized arguments would be very powerful. >> >> It is syntactically cleaner at the site of the function declaration for >> sure. Wouldn’t you agree that it is semantically less clear though? Both >> are important, but which is more important? If not semantics, why not? >> >> It’s also worth noting that it would be more verbose in at least some use >> cases (when a declaration such as `defaultConfiguration()` is used in the >> conditional default expression and is not otherwise necessary). >> >> (Apologies for the earlier blank reply—why is Cmd+Enter a keyboard shortcut >> for Send something someone would think is a good idea?) >> >> That verbosity is kind of a feature of my design, in the sense that it >> describes exactly what's going on at the location in code where someone >> expects to see default values. If anything, the default factory name is an >> opportunity to add context where normally there might be none. >> >> It's also worth noting that this design works well if you have multiple >> methods that need to share the same defaults. Instead of repeating the same >> annotations for each method that needs them, you just define the functions >> once and refer to them everywhere. (Could you do the same with the >> annotation based method? Probably, if you allow arbitrary expressions within >> them. But that seems less obvious compared to this approach.) > > You make a really good point here. The annotations would need to be applied > to every declaration that uses them whereas your proposed syntax would just > rely on overload resolution succeeding or failing in a given type context. > The use case I have would benefit in this respect as the conditional defaults > would be shared by several declarations. > > Howard’s idea of restricting conditional defaults to only use declarations in > the same file seems somewhat arbitrary but it would go a long way towards > helping an author understand clearly what the provided defaults are as the > rest of the module and imported symbols would not need to be considered. I > wonder if this approach could be refined a bit so it feels less arbitrary. > Users would probably need to rely on tooling to discover defaults but I think > I’m ok with that. I am mostly concerned with the ability of an author to > reason locally about the API contract they are publishing. Do you have any > ideas on how to refine Howard's idea? > > I mentioned earlier in the thread (with a few messed up details) that there > are already some restrictions on default arguments that I think already work > well here. Declarations referenced in the default value expression of a > function today must be accessible within the scope of the declaration being > defined, so I'm not sure if we need to go further than that. > > Here's an example that I'll admit is completely contrived, but should I be > prevented from doing this? Let's say I define an "Identities" module with > this type: > > ``` > enum Identities { > func identity() -> Int { return 0 } // let's ignore additive vs. > multiplicative for a moment > func identity() -> Double { return 0.0 } > func identity() -> () { return () } > func identity() -> String { return "" } > func identity<T>() -> (T) -> Void { return { _ in } } > // and so on > } > ``` > > Then somewhere I want to define a conditional default, using those identities: > > ``` > import Identities > > func someWeirdThing<T>( ...contrived args..., defaultValue: T =? > Identities.identity()) { ... } > ``` > > Swift today already allows this with regular default value expressions, so > the problem of tooling being needed to discover defaults already exists and > this hypothetical construct doesn't change that. We *could* restrict such > defaults to same file or same module and it seems reasonable to do so, but > should we? If the defaults I want happen to live elsewhere, why not let me > use them? Or, if it's a serious concern, why not lock down all default > expressions the same way?
You make a really good point about the current behavior default value expressions that I hadn’t fully considered. It is currently possible for a new, more specific declaration to be introduced that would be selected. It is also currently possible for the symbol that is resolved to be removed while a less specific declaration is available for resolution. I think the reason I hadn’t considered this is that they always resolve to the same type (possibly a generic T) and a default is always available. It seems unlikely that a change in overload resolution in this context would be problematic. Dave Abrahams summed up my reluctance to embrace your proposed solution very concisely: > This sort of “it compiles if it’s syntactically valid, regardless of declared > constraints” thing is deliberately avoided in Swift’s generics design with > good reason; it’s possible that in this instance there are no problems, but > I’m skeptical. You are effectively proposing that in this very narrow case we perform overload resolution on a symbol in a generic type context *after* the generic type has been replaced with a concrete type. In every other case Swift has been very intentionally designed to resolve the symbol in a generic type context using the known constraints on the generic type. Do you agree that this is a good rule in general? If so, why should we make an exception to it? If not, why not? > > I think the bigger concern is the other one Xiaodi mentioned—we probably > don't want people to be able to retroactively add overloads that would > introduce a default where previously there was none. (This wouldn't be > possible for global functions in different modules, but could be for > non-private type members.) This might be something that Just Works Out™ > depending on how and when the compiler resolves such expressions—but I don't > know the compiler deeply enough to say for sure without investigating more. I think impact would be limited to the current module but I am also concerned that “this might be something that Just Works Out™” isn’t good enough. IMO, we need to know or sure that it will work out ok in this context before we consider it. Please don't misunderstand - I appreciate the elegant syntax of the design you are proposing. But I would hate to see it adopted only to have us regret the its semantics afterwards. > > > > >> >> >>> >>> -- Howard. >>> >>> On 26 Nov 2017, at 9:25 am, Tony Allevato via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>> >>>> On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi...@gmail.com >>>> <mailto:xiaodi...@gmail.com>> wrote: >>>> On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matt...@anandabits.com >>>> <mailto:matt...@anandabits.com>> wrote: >>>>> On Nov 25, 2017, at 1:28 PM, Tony Allevato via swift-evolution >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>> >>>>> On Fri, Nov 24, 2017 at 7:18 PM Xiaodi Wu via swift-evolution >>>>> <swift-evolution@swift.org <mailto: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.0 >>>> >>>> func raise(_ x: Double, to exponent: Double = defaultExponent) { >>>> print(pow(x, exponent)) >>>> } >>>> } >>>> >>>> Foo().raise(4) // "16.0" >>>> Foo.defaultExponent = 3.0 >>>> Foo().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. >>>> >>>> >>>> >>>> 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 <mailto: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 @overload >>>>> >>>>> ``` >>>>> struct 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 <mailto: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 >>>>> <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: >>>>> >>>>> ```swift >>>>> 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 >>>>> (https://github.com/apple/swift/pull/13012#discussion_r152124535 >>>>> <https://github.com/apple/swift/pull/13012#discussion_r152124535>). >>>>> >>>>> 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 <mailto:swift-evolution@swift.org> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> >>>>> >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>> _______________________________________________ >>> swift-evolution mailing list >>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution