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

Tony Allevato tony.allevato at gmail.com
Tue Nov 28 11:40:47 CST 2017


On Tue, Nov 28, 2017 at 9:16 AM Matthew Johnson <matthew at anandabits.com>
wrote:

> On Nov 28, 2017, at 11:01 AM, Tony Allevato <tony.allevato at gmail.com>
> wrote:
>
>
>
> On Tue, Nov 28, 2017 at 8:45 AM Matthew Johnson <matthew at anandabits.com>
> wrote:
>
>> On Nov 28, 2017, at 10:06 AM, Tony Allevato <tony.allevato at gmail.com>
>> wrote:
>>
>>
>>
>> On Mon, Nov 27, 2017 at 10:32 PM Slava Pestov <spestov at apple.com> wrote:
>>
>>> Hi Tony,
>>>
>>> So if my understanding is correct, the basic proposal is the following:
>>>
>>> func id<T>(t: T ?= T.defaultValue) { return t }
>>>
>>> extension Int { static var defaultValue = 0 }
>>>
>>> extension String { static var defaultValue = “” }
>>>
>>> id() as Int // returns 0
>>> id() as String // returns “”
>>> id() as SomeRandomType // fails to type check — no default argument
>>>
>>> I don’t understand what would happen if the caller is itself generic
>>> though, for example:
>>>
>>> callsID<T>(_ t: T) {
>>>   _ = id() as T
>>> }
>>>
>>> It appears that body of callsID() itself cannot type check without
>>> knowledge of the concrete T that will be used with this function.
>>>
>>
>> Thanks for bringing up this example, Slava.
>>
>> Unless I'm misunderstanding, the issue you're describing is inherent to
>> the *problem* described in the original post, not to any specific
>> hypothetical syntax for adding the default arguments, correct? In other
>> words, if this was written using extensions as can be done today:
>>
>> ```
>> struct Foo<T> {
>>   func id(t: T) -> T { return t }
>> }
>>
>> extension Foo where T == Void {
>>   func id() -> T { return () }
>> }
>>
>> extension Foo where T == Int {
>>   func id() -> T { return 0 }
>> }
>>
>> callsID<T>(_ t: T) {
>>   _ = Foo().id() as T    // mark
>> }
>> ```
>>
>> The compiler would still reject the marked line because there's no
>> guarantee that T is one of the types that has the necessary overload.
>>
>> But now that you've mentioned it, it does have me thinking that this
>> problem might be better left to extensions. In one sense, default arguments
>> are a kind of "overload synthesis”,
>>
>>
>> They appear to callers as if they were overloads but I think it’s
>> important that they actually do so *without* introducing an overload.
>> Reducing the size of an overload set is good for users, library authors and
>> the compiler.  The benefits that come to all parties when the size of an
>> overload set is reduced is the primary reason I started this thread.
>>
>> but on the other hand, there's an expectation that the default value
>> expression is of a single type (or set of related types) known at compile
>> time. Even if it's generic, it still must be expressed in terms of whatever
>> constraints are present on that generic type—you can't use a disjunction of
>> types, but instead have to have a common protocol that would provide some
>> operation.
>>
>>
>> This should not change for any given default value expression.  This
>> thread doesn’t discuss changing that.  I discusses the ability to constrain
>> the presence of a default value expression.
>>
>
> But that's exactly the distinction that I'm driving at—the more I think
> about it, the more I have difficulty expressing it cleanly and I think
> that's because these aren't the same as default arguments—they really are
> distinct overloads because you're talking about the presence or absence of
> an argument based on specific constraints instead of something that applies
> to the function uniformly for all possible types that satisfy the
> constraint. So what I'm suggesting is that maybe conflating this concept
> with default arguments is the wrong approach after all.
>
>
>
>>  While it would be useful to allow multiple default value expressions for
>> different constraints the most common case will be a single constrained
>> default value expression for any given argument.  We could just allow a
>> trailing where clause on the default value expression itself like this:
>>
>> func makeResource(
>>     with configuration: Configuration = () where Configuration == Void,
>>     actionHandler: @escaping (Action) -> Void = { _ in } where Action ==
>> Never
>> )
>>
>> That addresses the most common cases for this feature with a fairly
>> obvious and direct syntax.
>>
>
> Allowing only one constraint seems arbitrary and I imagine that the
> immediately following question would be "what if I want more than one?" I
> wouldn't want to address only part of the problem. My ICU example further
> up in the thread shows a scenario where I would want defaults for two
> distinct types (if I also wanted to publicize the general initializer to
> everyone).
>
>
> Did you really mean “only one constraint” here?  Or did you mean “only one
> default value expression”?  These are very different.  I would not want to
> restrict the number of constraints.  What I am suggesting is that limiting
> this to a single (possibly constrained) default value expression presents a
> relatively obvious syntactic solution.
>

Yes, I probably should have said "one where clause". Regardless, such a
restriction feels incomplete.


>
>
>
>
>>  It also avoids the potential ambiguity that could arise from allowing
>> multiple defaults with different (potentially overlapping) constraints.
>>
>
> That would be no different than the existing problem of multiple overloads
> with different (potentially overlapping) constraints, right?
>
>
> Right, but...
>
> If what you're looking for is a way to have the compiler automatically
> synthesize overloads in extensions for you based on certain constraints, I
> would expect the same restrictions to apply as if you had explicitly
> written them out long-form.
>
>
> I specifically *do not* want the compiler to synthesize overloads.  I want
> to get rid of them entirely, not just the code that declares them.  What I
> would like the compiler to do is to synthesize the default value expression
> at call sites that meet the constraints for the default value expression
> and omit an explicit argument (exactly as it does for unconstrained default
> value expressions).
>
> These are very different things.  The latter is not possible in Swift
> today.  It can only be emulated by providing an unnecessarily bloated
> overload set.
>

So you specifically want the behavior for default arguments where a special
thunk is used to emit the value instead of writing (either manually or
synthesized) overloads? If that distinction is so vital to your use case
(limited to a single default value expression), then how do you rationalize
restricting it? Why shouldn't someone be able to provide default thunks for
N different sets of constraints, if they should need to? Wouldn't the same
reasoning apply there?



>
> The more I think about this the more simply allowing a where clause on a
> default value expression feels like the right thing to do (if we introduce
> this feature).  It doesn’t solve all potential use cases but it does solve
> the 80% case in an obvious way and doesn’t harm the current workaround in
> the remaining cases.
>
>
>
>
>>
>>
>>
>>>
>>> Slava
>>>
>>> On Nov 27, 2017, at 4:10 PM, Tony Allevato via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>> I totally agree that that's a good rule in general—I'm not 100%
>>> comfortable making an exception to it for this, but I wanted to start a
>>> discussion about a different approach than had been considered so far.
>>>
>>> The idea of forcing the user to acknowledge the explicitness of SFINAE
>>> with a strawman syntax `=?` instead of `=` was a thought experiment to
>>> bridge the wild-west-C++ world of templates and Swift's stricter generics,
>>> but I can definitely understand if even that kind of approach is something
>>> that the core team (who are far more familiar with the C++ side of that
>>> coin than I am) doesn't wish to support. As was pointed out, it's not
>>> something Swift supports anywhere else today.
>>>
>>> If we look at it from that point of view, where such a semantic
>>> treatment of generics would not be supported, I think it becomes a lot
>>> harder to rationalize treating this as "default arguments". What you really
>>> do have (and what writing it as constrained extensions makes clear) is
>>> additional overloads, because they only apply to certain subsets of types.
>>> If that's the case, maybe it's the wrong approach to try to turn overloads
>>> into "partial default values".
>>>
>>>
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171128/fc2bc7b1/attachment.html>


More information about the swift-evolution mailing list