[swift-evolution] [Pre-pitch] Conditional default arguments

Xiaodi Wu xiaodi.wu at gmail.com
Sat Nov 25 16:34:41 CST 2017


On Sat, Nov 25, 2017 at 4:25 PM, Tony Allevato <tony.allevato at gmail.com>
wrote:

> On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
>> On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew at anandabits.com>
>> wrote:
>>
>>> On Nov 25, 2017, at 1:28 PM, Tony Allevato via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>> On Fri, Nov 24, 2017 at 7:18 PM Xiaodi Wu via swift-evolution <
>>> swift-evolution at 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 at 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 at 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 at swift.org
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>
>>>>>>
>>>>>
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>
>>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>
>>>
>>>>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171125/4d47eb59/attachment.html>


More information about the swift-evolution mailing list