[Pre-pitch] Conditional default arguments

Yeah, I realized that wasn’t true after sending it—it could come from an
imported module, as long as it’s visible. Still, I imagine that retroactive
conformance wouldn’t be an issue because when the compiler type checks the
default at the declaration site, it would only see declarations that it
already knows about.

As for locality, yes, that’s a drawback of my approach, but I still would
prefer it to an odd-looking annotation based approach that introduced a
whole new and different syntax for certain kinds of default arguments.

···

On Sat, Nov 25, 2017 at 3:18 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

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

On Sat, Nov 25, 2017 at 2:35 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

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

On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@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?

Well, it's a pitch, not a complete design, so it's either possible or not
possible depending on what restrictions we place on it :)

You're right that if this was implemented in a certain way, someone could
add overloads in other modules that would allow defaults to exist where
they otherwise wouldn't. If that's a concern, then the answer is
simple—have the compiler only look in the same module for matching
functions.

That gets rid of retroactive conformance as a moving piece, but it still
disperses information about presence or absence of a default argument.
Today, that information is contained at the declaration of the function:
either there is `= default` or there isn't. Your design would still require
looking through an entire module (imagine, scrolling through all of
Foundation) to assure oneself that there is not some extension elsewhere
that impacts the number of default arguments.

A function used in a default value expression today must be present in
the same module or file by virtue of the fact that if it wasn't, the
compiler wouldn't be able to reference it,

Not at all:

// File A:
struct T {
  func f(_ i: Int = T.i()) {
    print(i)
  }
}

T().f()

// File B:
extension T {
  static func i() -> Int { return 42 }
}

It doesn't even have to be in the same module.

so this would be somewhat consistent with that behavior.

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:

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 (
Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

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).

···

On Nov 27, 2017, at 3:56 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

-- 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.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@anandabits.com <mailto:matthew@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\). 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:

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 (Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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?

···

On Nov 27, 2017, at 4:25 PM, Tony Allevato <tony.allevato@gmail.com> wrote:
On Mon, Nov 27, 2017 at 2:19 PM Matthew Johnson <matthew@anandabits.com <mailto:matthew@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:

-- 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.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@anandabits.com <mailto:matthew@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\). 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:

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 (Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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.

···

On Nov 27, 2017, at 4:55 PM, Tony Allevato <tony.allevato@gmail.com> wrote:
On Mon, Nov 27, 2017 at 2:39 PM Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Nov 27, 2017, at 4:25 PM, Tony Allevato <tony.allevato@gmail.com <mailto:tony.allevato@gmail.com>> wrote:
On Mon, Nov 27, 2017 at 2:19 PM Matthew Johnson <matthew@anandabits.com <mailto:matthew@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:

-- 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.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@anandabits.com <mailto:matthew@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\). 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:

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 (Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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).

-- Howard.

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.

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.

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…

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:

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 (
Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

···

On Mon, Nov 27, 2017 at 2:19 PM Matthew Johnson <matthew@anandabits.com> wrote:

On Nov 27, 2017, at 3:56 PM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:
On 26 Nov 2017, at 9:25 am, Tony Allevato via swift-evolution < > swift-evolution@swift.org> wrote:
On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@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:

On Fri, Nov 24, 2017 at 8:36 PM, T.J. Usiyan via swift-evolution < >>>> swift-evolution@swift.org> wrote:

On Fri, Nov 24, 2017 at 6:11 PM, Matthew Johnson via swift-evolution < >>>>> 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.)

···

On Mon, Nov 27, 2017 at 2:19 PM Matthew Johnson <matthew@anandabits.com> wrote:

On Nov 27, 2017, at 3:56 PM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:

-- Howard.

On 26 Nov 2017, at 9:25 am, Tony Allevato via swift-evolution < > swift-evolution@swift.org> wrote:

On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@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.

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:

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 (
Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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.

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".

Keep in mind that in general, this would require runtime support — we don’t always know the concrete substitution for a generic parameter at compile time, especially in the presence of separate compilation (but even without, for instance when optimizations are not enabled or unable to recover concrete type information).

Slava

···

On Nov 27, 2017, at 3:38 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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.

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?

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.

···

On Mon, Nov 27, 2017 at 2:39 PM Matthew Johnson <matthew@anandabits.com> wrote:

On Nov 27, 2017, at 4:25 PM, Tony Allevato <tony.allevato@gmail.com> > wrote:
On Mon, Nov 27, 2017 at 2:19 PM Matthew Johnson <matthew@anandabits.com> > wrote:

On Nov 27, 2017, at 3:56 PM, Howard Lovatt via swift-evolution < >> swift-evolution@swift.org> wrote:

-- Howard.

On 26 Nov 2017, at 9:25 am, Tony Allevato via swift-evolution < >> swift-evolution@swift.org> wrote:

On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@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.

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:

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 (
Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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 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".

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[image:
™] 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[image: ™]”
isn’t good enough. IMO, we need to know or sure that it will work out ok
in this context before we consider it.

I think I'm being misunderstood here. Naturally I'm not saying we should
just go full-speed into an approach and hope that it "just works out". What
I'm saying is, I think there's potential with that approach that I don't
have enough knowledge in that area and I'm hoping someone with more
knowledge will chime into the discussion. :)

So to be clear, I'm not suggesting that my proposed approach, unmodified,
is the way we should definitely go. My intention is just to pitch something
that aims to be closer to how defaults work otherwise in the language, and
try to find a way to fit this use case into it, rather than wedging in an
@annotation.

···

On Mon, Nov 27, 2017 at 3:38 PM Matthew Johnson <matthew@anandabits.com> wrote:

On Nov 27, 2017, at 4:55 PM, Tony Allevato <tony.allevato@gmail.com> > wrote:
On Mon, Nov 27, 2017 at 2:39 PM Matthew Johnson <matthew@anandabits.com> > wrote:

On Nov 27, 2017, at 4:25 PM, Tony Allevato <tony.allevato@gmail.com> >> wrote:
On Mon, Nov 27, 2017 at 2:19 PM Matthew Johnson <matthew@anandabits.com> >> wrote:

On Nov 27, 2017, at 3:56 PM, Howard Lovatt via swift-evolution < >>> swift-evolution@swift.org> wrote:

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> wrote:

On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@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.

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:

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 (
Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

You could even restrict to the same file, like extension access to private.

-- Howard.

···

On 26 Nov 2017, at 9:44 am, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

On Sat, Nov 25, 2017 at 2:35 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

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

On Sat, Nov 25, 2017 at 1:16 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sat, Nov 25, 2017 at 15:06 Matthew Johnson <matthew@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.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.

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?

Well, it's a pitch, not a complete design, so it's either possible or not possible depending on what restrictions we place on it :)

You're right that if this was implemented in a certain way, someone could add overloads in other modules that would allow defaults to exist where they otherwise wouldn't. If that's a concern, then the answer is simple—have the compiler only look in the same module for matching functions. A function used in a default value expression today must be present in the same module or file by virtue of the fact that if it wasn't, the compiler wouldn't be able to reference it, so this would be somewhat consistent with that behavior.

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:

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 (Add generic associatedtypes and generalized supertype constraints by anandabits · Pull Request #13012 · apple/swift · GitHub).

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

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.

Keep in mind that in general, this would require runtime support — we don’t always know the concrete substitution for a generic parameter at compile time, especially in the presence of separate compilation (but even without, for instance when optimizations are not enabled or unable to recover concrete type information).

Thanks for mentioning that. IMO, it rules out this approach as it means we wouldn’t always know statically whether a default argument is available. C++ gets away with it because templates are always substituted during compilation and that isn’t true for Swift generics.

···

On Nov 28, 2017, at 12:34 AM, Slava Pestov <spestov@apple.com> wrote:

On Nov 27, 2017, at 3:38 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Slava

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", 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.

···

On Mon, Nov 27, 2017 at 10:32 PM Slava Pestov <spestov@apple.com> wrote:

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".

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. 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. It also avoids the potential ambiguity that could arise from allowing multiple defaults with different (potentially overlapping) constraints.

···

On Nov 28, 2017, at 10:06 AM, Tony Allevato <tony.allevato@gmail.com> wrote:
On Mon, Nov 27, 2017 at 10:32 PM Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

Slava

On Nov 27, 2017, at 4:10 PM, Tony Allevato via swift-evolution <swift-evolution@swift.org <mailto: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".

Similar question to the one I posed earlier — what happens if I’m using makeResource() from a generic context? Is the conditional default argument simply not available?

In this case, how is it different from defining some static overloads of makeResource(), some of which have default arguments and some of which are generic?

Slava

···

On Nov 28, 2017, at 8:44 AM, Matthew Johnson <matthew@anandabits.com> wrote:

func makeResource(
    with configuration: Configuration = () where Configuration == Void,
    actionHandler: @escaping (Action) -> Void = { _ in } where Action == Never
)

func makeResource(
    with configuration: Configuration = () where Configuration == Void,
    actionHandler: @escaping (Action) -> Void = { _ in } where Action == Never
)

Similar question to the one I posed earlier — what happens if I’m using makeResource() from a generic context? Is the conditional default argument simply not available?

Right. If the constraints are not met at the call site the default is not available. I think you understood, but looking at the example above I omitted the resource type parameter. It should read:

func makeResource<R: Resource>(
    with configuration: R.Configuration = () where R.Configuration == Void,
    actionHandler: @escaping (R.Action) -> Void = { _ in } where R.Action == Never
)

In this case, how is it different from defining some static overloads of makeResource(), some of which have default arguments and some of which are generic?

From the point of view of the call site it is not different. The differences are that:

* the user is presented with a single API rather than several overloads

Is this less confusing than encountering a new ‘where’ clause on default arguments, which is probably rare enough that many users will spend months/years using Swift without seeing it?

* the compiler doesn’t have to reason about an overload set which might improve build times, etc

They’re effectively equivalent, because we still have to decide which subset of the default arguments apply at a given call site by checking all combinations of constraints.

Slava

···

On Nov 28, 2017, at 1:25 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Nov 28, 2017, at 3:18 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Nov 28, 2017, at 8:44 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Slava

Probably not. In general I’m wary of designing language features specifically to speed up the type checker, since they make not have the intended effect or even the opposite effect. We know the type checker implementation is not the best possible implementation of a type checker — there is a lot we can improve without changing the language.

Slava

···

On Nov 28, 2017, at 1:35 PM, Matthew Johnson <matthew@anandabits.com> wrote:

* the compiler doesn’t have to reason about an overload set which might improve build times, etc

They’re effectively equivalent, because we still have to decide which subset of the default arguments apply at a given call site by checking all combinations of constraints.

Interesting. Are there no advantages to the compiler that would be possible if an overload set was replaced with constrained default arguments?

func makeResource(
    with configuration: Configuration = () where Configuration == Void,
    actionHandler: @escaping (Action) -> Void = { _ in } where Action == Never
)

Similar question to the one I posed earlier — what happens if I’m using makeResource() from a generic context? Is the conditional default argument simply not available?

Right. If the constraints are not met at the call site the default is not available. I think you understood, but looking at the example above I omitted the resource type parameter. It should read:

func makeResource<R: Resource>(
    with configuration: R.Configuration = () where R.Configuration == Void,
    actionHandler: @escaping (R.Action) -> Void = { _ in } where R.Action == Never
)

In this case, how is it different from defining some static overloads of makeResource(), some of which have default arguments and some of which are generic?

From the point of view of the call site it is not different. The differences are that:

* the user is presented with a single API rather than several overloads
* the library author isn’t required to maintain an overload set
* the compiler doesn’t have to reason about an overload set which might improve build times, etc

···

On Nov 28, 2017, at 3:18 PM, Slava Pestov <spestov@apple.com> wrote:

On Nov 28, 2017, at 8:44 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Slava

func makeResource(
    with configuration: Configuration = () where Configuration == Void,
    actionHandler: @escaping (Action) -> Void = { _ in } where Action == Never
)

Similar question to the one I posed earlier — what happens if I’m using makeResource() from a generic context? Is the conditional default argument simply not available?

Right. If the constraints are not met at the call site the default is not available. I think you understood, but looking at the example above I omitted the resource type parameter. It should read:

func makeResource<R: Resource>(
    with configuration: R.Configuration = () where R.Configuration == Void,
    actionHandler: @escaping (R.Action) -> Void = { _ in } where R.Action == Never
)

In this case, how is it different from defining some static overloads of makeResource(), some of which have default arguments and some of which are generic?

From the point of view of the call site it is not different. The differences are that:

* the user is presented with a single API rather than several overloads

Is this less confusing than encountering a new ‘where’ clause on default arguments, which is probably rare enough that many users will spend months/years using Swift without seeing it?

I think so. The where clause is used for constraints consistently in the language so the meaning of seeing one attached to a default argument should be intuitive for anyone familiar with the generics system. In the motivating use case I have there would still be an overload set available but it would be much smaller with this feature and therefore the available signatures would be much more clear.

* the compiler doesn’t have to reason about an overload set which might improve build times, etc

They’re effectively equivalent, because we still have to decide which subset of the default arguments apply at a given call site by checking all combinations of constraints.

Interesting. Are there no advantages to the compiler that would be possible if an overload set was replaced with constrained default arguments?

···

On Nov 28, 2017, at 3:28 PM, Slava Pestov <spestov@apple.com> wrote:

On Nov 28, 2017, at 1:25 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Nov 28, 2017, at 3:18 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Nov 28, 2017, at 8:44 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Slava

Slava

* the compiler doesn’t have to reason about an overload set which might improve build times, etc

They’re effectively equivalent, because we still have to decide which subset of the default arguments apply at a given call site by checking all combinations of constraints.

Interesting. Are there no advantages to the compiler that would be possible if an overload set was replaced with constrained default arguments?

Probably not. In general I’m wary of designing language features specifically to speed up the type checker, since they make not have the intended effect or even the opposite effect. We know the type checker implementation is not the best possible implementation of a type checker — there is a lot we can improve without changing the language.

That isn’t the motivation here. I thought it might be an incidental benefit. If it isn’t the motivating use case still stands. Of course it may or may not be sufficient to justify the feature. :)

···

On Nov 28, 2017, at 4:11 PM, Slava Pestov <spestov@apple.com> wrote:

On Nov 28, 2017, at 1:35 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Slava