On Tue, Nov 28, 2017 at 9:16 AM Matthew Johnson <matt...@anandabits.com> wrote:
> On Nov 28, 2017, at 11:01 AM, Tony Allevato <tony.allev...@gmail.com> > wrote: > > > > On Tue, Nov 28, 2017 at 8:45 AM Matthew Johnson <matt...@anandabits.com> > wrote: > >> On Nov 28, 2017, at 10:06 AM, Tony Allevato <tony.allev...@gmail.com> >> wrote: >> >> >> >> On Mon, Nov 27, 2017 at 10:32 PM Slava Pestov <spes...@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@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". >>> >>> >>>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution