> 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

Reply via email to