On Sat, Nov 25, 2017 at 4:25 PM, Tony Allevato <tony.allev...@gmail.com> wrote:
> On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi...@gmail.com> wrote: > >> On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matt...@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.0 > > func 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? > > >> 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 @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> 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: >>>>>> >>>>>> ```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). >>>>>> >>>>>> 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 >>> >>>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution