[Pre-pitch] Conditional default arguments

(Matthew Johnson) #1

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 (https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it be discussed on list is that I haven’t thought of a good way to express this syntactically. I am interested in hearing general feedback on the idea. I am also looking for syntax suggestions.

Matthew

Can a meta-type parameter have a default value?
Improving the UI of generics
(TJ Usiyan) #2

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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it
be discussed on list is that I haven’t thought of a good way to express
this syntactically. I am interested in hearing general feedback on the
idea. I am also looking for syntax suggestions.

Matthew

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

(Jon Hull) #3

I have wanted something similar to this for a while, and haven’t suggested it for a while for the same reason… I can’t really think of a good syntax.

My best idea so far is to put it after the function declaration but before the open brace:

  func myCrazyFunction<T>(myParam: T)->Int
  @default myParam = 0 where T == Int.Self
  @default myParam = “” where T == String.Self
  {
    //Function body goes here
  }

When multiple options are available, it would match the most specific one that qualifies. The idea I had would also allow it to have expressions on the rhs, and would potentially allow other input types outside of T (as long as the rhs returns T):

  func evenCrazier(myParam: String)->Int
  @default myParam = “\($0)” where myParam : CustomStringConvertible
  {
    //Function body goes here
  }

There are some obvious issues with the syntax here (e.g. I am using $0), but I think the general idea is a really useful one. That is, I am able to provide a conversion formula that lets someone pass anything which is customStringConvertible to this String parameter and have it pass the appropriate String to my actual function. I would use this constantly, and it would prevent the combinatorial explosion of overloads I have now…

This also gets rid of the need for the often-requested-but-never-going-to-happen union types:

  enum MyEnum {
    case int (Int)
    case str (String)
  }

  func noNeedForUnion(_ intOrStr: MyEnum)
  @default intOrStr = .int($0) where intOrStr == Int.Self
  @default intOrStr = .str($0) where intOrStr == String.Self
  {
    //Function body here
  }

  noNeedForUnion(3) //This passes .int(3)
  noNeedForUnion(“Hey”) //this passes .str(“Hey”)

I could see this making a bunch of frameworks nicer to use… (or at least nicer to write)

Thanks,
Jon

···

On Nov 24, 2017, at 3: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 (https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it be discussed on list is that I haven’t thought of a good way to express this syntactically. I am interested in hearing general feedback on the idea. I am also looking for syntax suggestions.

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

(Douglas Gregor) #4

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.

If one could refer to `self` in a default argument (which is not a big problem), you could turn the default into a requirement itself… although it doesn’t *quite* work with your example as written because it would always need to be implemented somehow:

protocol Resource {
  associatedtype Configuration
  associatedtype Action

    func defaultConfiguration() -> Configuration
    func defaultHandler() -> ((R.Action) -> Void)

}

Doug mentioned that it may also help simplify associated type inference (https://github.com/apple/swift/pull/13012#discussion_r152124535).

Oh, I thought this was something related to choosing a defaults for associated types, which might have helped with my current associated-type-inference quandary. The topic you actually wanted to discuss is disjoint (sorry).

  - Doug

···

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

(Xiaodi Wu) #5

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.

···

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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it
be discussed on list is that I haven’t thought of a good way to express
this syntactically. I am interested in hearing general feedback on the
idea. I am also looking for syntax suggestions.

Matthew

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

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

(Tony Allevato) #6

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.

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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it
be discussed on list is that I haven’t thought of a good way to express
this syntactically. I am interested in hearing general feedback on the
idea. I am also looking for syntax suggestions.

Matthew

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

(Matthew Johnson) #7

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.

If one could refer to `self` in a default argument (which is not a big problem), you could turn the default into a requirement itself… although it doesn’t *quite* work with your example as written because it would always need to be implemented somehow:

protocol Resource {
  associatedtype Configuration
  associatedtype Action

    func defaultConfiguration() -> Configuration
    func defaultHandler() -> ((R.Action) -> Void)

}

This won’t work on its own for this use case because there is only a valid default in relatively narrow (but common) cases. For most values of Configuration and Action an argument must be provided by the caller. Xiaodi’s proposed syntax is the best fit (so far) for the use case I had in mind.

That said, the ability to refer to self in default arguments is complementary as it would expand the cases where conditional default arguments could be provided. For example, in the example above it would allow a resource to provide a nontrivial default configuration.

Doug mentioned that it may also help simplify associated type inference (https://github.com/apple/swift/pull/13012#discussion_r152124535).

Oh, I thought this was something related to choosing a defaults for associated types, which might have helped with my current associated-type-inference quandary. The topic you actually wanted to discuss is disjoint (sorry).

I was wondering how it was related and wondered if it was somehow due to reduction in the size of the overload set. If you have any ideas on changes that might help guide the inference algorithm somehow please start a new thread. Even if you’re only able to describe the challenges it might be worth a thread if it’s possible others might have useful ideas. Improving the reliability and predictability of inference is a very important topic!

···

On Nov 27, 2017, at 12:50 PM, Douglas Gregor <dgregor@apple.com> wrote:

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

  - Doug

(Matthew Johnson) #8

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.

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 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 <mailto:swift-evolution@swift.org>> wrote:

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 (https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it be discussed on list is that I haven’t thought of a good way to express this syntactically. I am interested in hearing general feedback on the idea. I am also looking for syntax suggestions.

Matthew

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

_______________________________________________
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

(Xiaodi Wu) #9

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.

I think I prefer Xiaodi’s suggestion for that reason. His approach could

···

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:
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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested
it be discussed on list is that I haven’t thought of a good way to express
this syntactically. I am interested in hearing general feedback on the
idea. I am also looking for syntax suggestions.

Matthew

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

(Thorsten Seitz) #10

I like Tony's idea. Maybe the default argument functions could even take arguments
with earlier parameters from the parameter list.

-Thorsten

···

Am 25.11.2017 um 20:28 schrieb Tony Allevato via swift-evolution <swift-evolution@swift.org>:

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.

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 (https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it be discussed on list is that I haven’t thought of a good way to express this syntactically. I am interested in hearing general feedback on the idea. I am also looking for syntax suggestions.

Matthew

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

(Tony Allevato) #11

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.

If one could refer to `self` in a default argument (which is not a big
problem), you could turn the default into a requirement itself… although it
doesn’t *quite* work with your example as written because it would always
need to be implemented somehow:

protocol Resource {
  associatedtype Configuration
  associatedtype Action

    func defaultConfiguration() -> Configuration
    func defaultHandler() -> ((R.Action) -> Void)

}

This won’t work on its own for this use case because there is only a valid
default in relatively narrow (but common) cases. For most values of
Configuration and Action an argument must be provided by the caller.
Xiaodi’s proposed syntax is the best fit (so far) for the use case I had in
mind.

Out of curiosity, what part of my proposed syntax misses the mark for your
use case?

The parts that I think are somewhat unfortunate are losing a small bit of
reference locality and the introduction of a new operator to distinguish
between "default argument not present if no match found" vs. "compile time
error", definitely. However, one unfortunate trend I've noticed when new
features are proposed is that there's a tendency to end up with "let's
invent @yet_another_attribute" and that just doesn't seem scalable or clean
if we can strive to better integrate it into the syntax of the language.

I'm particularly interested in this because I hit a use case that's similar
to yours in my own code base. I wanted a generic type that could be
instantiated with a disjoint type set—either a RawRepresentable whose
RawValue is Int, or an Int itself. This could have been solved by having
Int retroactively conform to RawRepresentable, but doing that to a
fundamental built-in type Feels Dirty™, so I made it work by not
constraining the generic type at all and moving the public initializers to
constrained extensions so that the only way you could *instantiate* the
type is if you have a correct type argument, and those initializers pass
through to an internal one, sending along a closure that does the correct
transformation:
https://github.com/allevato/icu-swift/blob/master/Sources/ICU/RuleBasedBreakCursor.swift#L208

With your proposed addition, I could hoist those closures into default
arguments based on the constraints instead of adding extensions.

That being said, I don't find the extension-based approach *that*
restrictive, and it kind of makes sense from the point of view of "this
overload only exists for this type under a particular constraint". I wonder
how often this comes up that combinatorial explosion is truly harmful.

···

On Mon, Nov 27, 2017 at 11:12 AM Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

On Nov 27, 2017, at 12:50 PM, Douglas Gregor <dgregor@apple.com> wrote:
On Nov 24, 2017, at 3:11 PM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

That said, the ability to refer to self in default arguments is
complementary as it would expand the cases where conditional default
arguments could be provided. For example, in the example above it would
allow a resource to provide a nontrivial default configuration.

Doug mentioned that it may also help simplify associated type inference (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

Oh, I thought this was something related to choosing a defaults for
associated types, which might have helped with my current
associated-type-inference quandary. The topic you actually wanted to
discuss is disjoint (sorry).

I was wondering how it was related and wondered if it was somehow due to
reduction in the size of the overload set. If you have any ideas on
changes that might help guide the inference algorithm somehow please start
a new thread. Even if you’re only able to describe the challenges it might
be worth a thread if it’s possible others might have useful ideas.
Improving the reliability and predictability of inference is a very
important topic!

- Doug

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

(Dave Abrahams) #12

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.

···

Sent from my iPhone

On Nov 25, 2017, at 1:16 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> 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.

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 (https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it be discussed on list is that I haven’t thought of a good way to express this syntactically. I am interested in hearing general feedback on the idea. I am also looking for syntax suggestions.

Matthew

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

(Tony Allevato) #13

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.

···

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:

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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested
it be discussed on list is that I haven’t thought of a good way to express
this syntactically. I am interested in hearing general feedback on the
idea. I am also looking for syntax suggestions.

Matthew

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

(Matthew Johnson) #14

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.

If one could refer to `self` in a default argument (which is not a big problem), you could turn the default into a requirement itself… although it doesn’t *quite* work with your example as written because it would always need to be implemented somehow:

protocol Resource {
  associatedtype Configuration
  associatedtype Action

    func defaultConfiguration() -> Configuration
    func defaultHandler() -> ((R.Action) -> Void)

}

This won’t work on its own for this use case because there is only a valid default in relatively narrow (but common) cases. For most values of Configuration and Action an argument must be provided by the caller. Xiaodi’s proposed syntax is the best fit (so far) for the use case I had in mind.

Out of curiosity, what part of my proposed syntax misses the mark for your use case?

I think it’s important that we be clear about when a default is and is not available. Your syntax makes the availability of a default non-local.

The parts that I think are somewhat unfortunate are losing a small bit of reference locality and the introduction of a new operator to distinguish between "default argument not present if no match found" vs. “com pile time error", definitely. However, one unfortunate trend I've noticed when new features are proposed is that there's a tendency to end up with "let's invent @yet_another_attribute" and that just doesn't seem scalable or clean if we can strive to better integrate it into the syntax of the language.

Xiaodi acknowledged that his suggestion is a bit clunky but I’m not sure we can do better while preserving clarity and making the defaults part of the function declaration. If we added this feature it would be relatively advanced and usually used by library developers. While I would be pleased with more elegant syntax I’m not sure there is a better solution. I prefer semantic clarity to syntactic elegance.

I'm particularly interested in this because I hit a use case that's similar to yours in my own code base. I wanted a generic type that could be instantiated with a disjoint type set—either a RawRepresentable whose RawValue is Int, or an Int itself. This could have been solved by having Int retroactively conform to RawRepresentable, but doing that to a fundamental built-in type Feels Dirty™, so I made it work by not constraining the generic type at all and moving the public initializers to constrained extensions so that the only way you could *instantiate* the type is if you have a correct type argument, and those initializers pass through to an internal one, sending along a closure that does the correct transformation: https://github.com/allevato/icu-swift/blob/master/Sources/ICU/RuleBasedBreakCursor.swift#L208

With your proposed addition, I could hoist those closures into default arguments based on the constraints instead of adding extensions.

IIUC you would still need the extensions because you only want to expose an initializer for two specific type arguments. Conditional default arguments would only work if you didn’t mind exposing an initializer for other type arguments (while requiring an explicit ruleStatusFactory for other type arguments).

That being said, I don't find the extension-based approach *that* restrictive, and it kind of makes sense from the point of view of "this overload only exists for this type under a particular constraint". I wonder how often this comes up that combinatorial explosion is truly harmful.

The extension based approach is workable, it’s just verbose and clunky and I suspect it results in useful default arguments being omitted in some cases. I’m sure the combinatorial problem is relatively rare but it leads to an overload set that is difficult to maintain properly when it happens. That said, if the community decides this is a niche problem that isn’t worth language support to solve I would understand that decision.

To be honest, this thread has received more positive feedback than I expected (I expected the generalized supertype thread to get more traffic). That indicates to me that the feature might not be quite as niche as I suspected. That is one of the things I hoped to learn from the thread.

···

On Nov 27, 2017, at 1:24 PM, Tony Allevato <tony.allevato@gmail.com> wrote:
On Mon, Nov 27, 2017 at 11:12 AM Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 27, 2017, at 12:50 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

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

That said, the ability to refer to self in default arguments is complementary as it would expand the cases where conditional default arguments could be provided. For example, in the example above it would allow a resource to provide a nontrivial default configuration.

Doug mentioned that it may also help simplify associated type inference (https://github.com/apple/swift/pull/13012#discussion_r152124535).

Oh, I thought this was something related to choosing a defaults for associated types, which might have helped with my current associated-type-inference quandary. The topic you actually wanted to discuss is disjoint (sorry).

I was wondering how it was related and wondered if it was somehow due to reduction in the size of the overload set. If you have any ideas on changes that might help guide the inference algorithm somehow please start a new thread. Even if you’re only able to describe the challenges it might be worth a thread if it’s possible others might have useful ideas. Improving the reliability and predictability of inference is a very important topic!

  - Doug

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

(Tony Allevato) #15

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.

If one could refer to `self` in a default argument (which is not a big
problem), you could turn the default into a requirement itself… although it
doesn’t *quite* work with your example as written because it would always
need to be implemented somehow:

protocol Resource {
  associatedtype Configuration
  associatedtype Action

    func defaultConfiguration() -> Configuration
    func defaultHandler() -> ((R.Action) -> Void)

}

This won’t work on its own for this use case because there is only a
valid default in relatively narrow (but common) cases. For most values of
Configuration and Action an argument must be provided by the caller.
Xiaodi’s proposed syntax is the best fit (so far) for the use case I had in
mind.

Out of curiosity, what part of my proposed syntax misses the mark for your
use case?

I think it’s important that we be clear about when a default is and is not
available. Your syntax makes the availability of a default non-local.

That's certainly true. It can be mitigated somewhat by placing the
definitions as close to the declaration as possible, but it's still up to
the user to do the right thing here:

struct ResourceDescription<R: Resource> {
  func defaultConfiguration() -> Void { return () }
  func defaultActionHandler() -> (Never) -> Void { return _ in }
  func makeResource(...) {
    ...
  }
}

It's worth noting that this problem does exist somewhat in the language
today, if you replace "makes the availability of a default non-local" with
"makes the value of a default non-local".

The parts that I think are somewhat unfortunate are losing a small bit of
reference locality and the introduction of a new operator to distinguish
between "default argument not present if no match found" vs. “com pile time
error", definitely. However, one unfortunate trend I've noticed when new
features are proposed is that there's a tendency to end up with "let's
invent @yet_another_attribute" and that just doesn't seem scalable or clean
if we can strive to better integrate it into the syntax of the language.

Xiaodi acknowledged that his suggestion is a bit clunky but I’m not sure
we can do better while preserving clarity and making the defaults part of
the function declaration. If we added this feature it would be relatively
advanced and usually used by library developers. While I would be pleased
with more elegant syntax I’m not sure there is a better solution. I prefer
semantic clarity to syntactic elegance.

Why not both? Hopefully we can get some more input from folks who might
have other ideas about how to bring the two ideas together.

For me, grafting on a second completely different default value syntax for
functions is hard to swallow, especially if folks feel that it's clunky
right out of the gate. (Especially if we're talking about parsing entire
expressions—and closure expressions—out of an attribute body. How deep does
that need to go?)

My thoughts on this particular problem are either that it's a relatively
advanced problem that comes up rarely enough that using extensions isn't a
significant problem and adding a special annotation isn't worth the
increase in compiler maintenance/complexity, or it's more common than we
think and thus warrants a syntax better integrated with the rest of the
language. It's probably hard to say for sure where this problem falls among
those two, but my guess it's that it's closer to the former than the
latter. If that's the case, I'd rather stick with what we have today rather
than create an ad hoc annotation for a rare use case.

I'm particularly interested in this because I hit a use case that's
similar to yours in my own code base. I wanted a generic type that could be
instantiated with a disjoint type set—either a RawRepresentable whose
RawValue is Int, or an Int itself. This could have been solved by having
Int retroactively conform to RawRepresentable, but doing that to a
fundamental built-in type Feels Dirty[image: ™], so I made it work by not
constraining the generic type at all and moving the public initializers to
constrained extensions so that the only way you could *instantiate* the
type is if you have a correct type argument, and those initializers pass
through to an internal one, sending along a closure that does the correct
transformation:
https://github.com/allevato/icu-swift/blob/master/Sources/ICU/RuleBasedBreakCursor.swift#L208

With your proposed addition, I could hoist those closures into default
arguments based on the constraints instead of adding extensions.

IIUC you would still need the extensions because you only want to expose
an initializer for two specific type arguments. Conditional default
arguments would only work if you didn’t mind exposing an initializer for
other type arguments (while requiring an explicit ruleStatusFactory for
other type arguments).

True, and I wouldn't mind that—the Int and RawRepresentable versions would
essentially be conveniences in that case.

···

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

On Nov 27, 2017, at 1:24 PM, Tony Allevato <tony.allevato@gmail.com> > wrote:
On Mon, Nov 27, 2017 at 11:12 AM Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote:

On Nov 27, 2017, at 12:50 PM, Douglas Gregor <dgregor@apple.com> wrote:
On Nov 24, 2017, at 3:11 PM, Matthew Johnson via swift-evolution < >> swift-evolution@swift.org> wrote:

That being said, I don't find the extension-based approach *that*
restrictive, and it kind of makes sense from the point of view of "this
overload only exists for this type under a particular constraint". I wonder
how often this comes up that combinatorial explosion is truly harmful.

The extension based approach is workable, it’s just verbose and clunky and
I suspect it results in useful default arguments being omitted in some
cases. I’m sure the combinatorial problem is relatively rare but it leads
to an overload set that is difficult to maintain properly when it happens.
That said, if the community decides this is a niche problem that isn’t
worth language support to solve I would understand that decision.

To be honest, this thread has received more positive feedback than I
expected (I expected the generalized supertype thread to get more
traffic). That indicates to me that the feature might not be quite as
niche as I suspected. That is one of the things I hoped to learn from the
thread.

That said, the ability to refer to self in default arguments is
complementary as it would expand the cases where conditional default
arguments could be provided. For example, in the example above it would
allow a resource to provide a nontrivial default configuration.

Doug mentioned that it may also help simplify associated type inference (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

Oh, I thought this was something related to choosing a defaults for
associated types, which might have helped with my current
associated-type-inference quandary. The topic you actually wanted to
discuss is disjoint (sorry).

I was wondering how it was related and wondered if it was somehow due to
reduction in the size of the overload set. If you have any ideas on
changes that might help guide the inference algorithm somehow please start
a new thread. Even if you’re only able to describe the challenges it might
be worth a thread if it’s possible others might have useful ideas.
Improving the reliability and predictability of inference is a very
important topic!

- Doug

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

(Xiaodi Wu) #16

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?

···

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:

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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested
it be discussed on list is that I haven’t thought of a good way to express
this syntactically. I am interested in hearing general feedback on the
idea. I am also looking for syntax suggestions.

Matthew

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

(Tony Allevato) #17

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

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.

···

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:

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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug
requested it be discussed on list is that I haven’t thought of a good way
to express this syntactically. I am interested in hearing general feedback
on the idea. I am also looking for syntax suggestions.

Matthew

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

(Matthew Johnson) #18

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.

If one could refer to `self` in a default argument (which is not a big problem), you could turn the default into a requirement itself… although it doesn’t *quite* work with your example as written because it would always need to be implemented somehow:

protocol Resource {
  associatedtype Configuration
  associatedtype Action

    func defaultConfiguration() -> Configuration
    func defaultHandler() -> ((R.Action) -> Void)

}

This won’t work on its own for this use case because there is only a valid default in relatively narrow (but common) cases. For most values of Configuration and Action an argument must be provided by the caller. Xiaodi’s proposed syntax is the best fit (so far) for the use case I had in mind.

Out of curiosity, what part of my proposed syntax misses the mark for your use case?

I think it’s important that we be clear about when a default is and is not available. Your syntax makes the availability of a default non-local.

That's certainly true. It can be mitigated somewhat by placing the definitions as close to the declaration as possible, but it's still up to the user to do the right thing here:

struct ResourceDescription<R: Resource> {
  func defaultConfiguration() -> Void { return () }
  func defaultActionHandler() -> (Never) -> Void { return _ in }
  func makeResource(...) {
    ...
  }
}

It's worth noting that this problem does exist somewhat in the language today, if you replace "makes the availability of a default non-local" with "makes the value of a default non-local”.

Non-locality of the value doesn’t bother me. The availability of the default is part of the API contract, the specific value need not be.

The parts that I think are somewhat unfortunate are losing a small bit of reference locality and the introduction of a new operator to distinguish between "default argument not present if no match found" vs. “com pile time error", definitely. However, one unfortunate trend I've noticed when new features are proposed is that there's a tendency to end up with "let's invent @yet_another_attribute" and that just doesn't seem scalable or clean if we can strive to better integrate it into the syntax of the language.

Xiaodi acknowledged that his suggestion is a bit clunky but I’m not sure we can do better while preserving clarity and making the defaults part of the function declaration. If we added this feature it would be relatively advanced and usually used by library developers. While I would be pleased with more elegant syntax I’m not sure there is a better solution. I prefer semantic clarity to syntactic elegance.

Why not both? Hopefully we can get some more input from folks who might have other ideas about how to bring the two ideas together.

Of course I agree! It would be wonderful if someone figured out how to accomplish that.

For me, grafting on a second completely different default value syntax for functions is hard to swallow, especially if folks feel that it's clunky right out of the gate. (Especially if we're talking about parsing entire expressions—and closure expressions—out of an attribute body. How deep does that need to go?)

I can sympathize with that point of view. The syntactic problem is that we don’t have an obvious way to attach constraints to default arguments inline, especially if different defaults were available for the same parameter under different constraints. We could place an attribute on the default itself like this:

`configuration: Configuration = @where((), Configuration == Void), @where(R.defaultConfiguration, R: DefaultConfigurable)`

However, this puts a lot of noise inline. I think Xiaodi’s suggestion of attaching an attribute to the function itself would lead to more readable declarations.

My thoughts on this particular problem are either that it's a relatively advanced problem that comes up rarely enough that using extensions isn't a significant problem and adding a special annotation isn't worth the increase in compiler maintenance/complexity, or it's more common than we think and thus warrants a syntax better integrated with the rest of the language. It's probably hard to say for sure where this problem falls among those two, but my guess it's that it's closer to the former than the latter. If that's the case, I'd rather stick with what we have today rather than create an ad hoc annotation for a rare use case.

That’s a reasonable point of view. I agree that it’s hard to say which case this falls under in general.

It’s worth noting that any parameter of an unconstrained generic type which will often be Void in practice is a parameter for which this feature leads to cleaner call-site code. It is often reasonable to provide nil as a default for Optional parameters. I don’t know of a way to identify whether an unconstrained T is an Optional but if that was possible this would be another relatively common use case for the feature.

I'm particularly interested in this because I hit a use case that's similar to yours in my own code base. I wanted a generic type that could be instantiated with a disjoint type set—either a RawRepresentable whose RawValue is Int, or an Int itself. This could have been solved by having Int retroactively conform to RawRepresentable, but doing that to a fundamental built-in type Feels Dirty<emoji_u2122.png>, so I made it work by not constraining the generic type at all and moving the public initializers to constrained extensions so that the only way you could *instantiate* the type is if you have a correct type argument, and those initializers pass through to an internal one, sending along a closure that does the correct transformation: https://github.com/allevato/icu-swift/blob/master/Sources/ICU/RuleBasedBreakCursor.swift#L208

With your proposed addition, I could hoist those closures into default arguments based on the constraints instead of adding extensions.

IIUC you would still need the extensions because you only want to expose an initializer for two specific type arguments. Conditional default arguments would only work if you didn’t mind exposing an initializer for other type arguments (while requiring an explicit ruleStatusFactory for other type arguments).

True, and I wouldn't mind that—the Int and RawRepresentable versions would essentially be conveniences in that case.

You can have that today if you expose the initializer you're forwarding to.

···

On Nov 27, 2017, at 3:27 PM, Tony Allevato <tony.allevato@gmail.com> wrote:
On Mon, Nov 27, 2017 at 12:49 PM Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Nov 27, 2017, at 1:24 PM, Tony Allevato <tony.allevato@gmail.com <mailto:tony.allevato@gmail.com>> wrote:
On Mon, Nov 27, 2017 at 11:12 AM Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Nov 27, 2017, at 12:50 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

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

That being said, I don't find the extension-based approach *that* restrictive, and it kind of makes sense from the point of view of "this overload only exists for this type under a particular constraint". I wonder how often this comes up that combinatorial explosion is truly harmful.

The extension based approach is workable, it’s just verbose and clunky and I suspect it results in useful default arguments being omitted in some cases. I’m sure the combinatorial problem is relatively rare but it leads to an overload set that is difficult to maintain properly when it happens. That said, if the community decides this is a niche problem that isn’t worth language support to solve I would understand that decision.

To be honest, this thread has received more positive feedback than I expected (I expected the generalized supertype thread to get more traffic). That indicates to me that the feature might not be quite as niche as I suspected. That is one of the things I hoped to learn from the thread.

That said, the ability to refer to self in default arguments is complementary as it would expand the cases where conditional default arguments could be provided. For example, in the example above it would allow a resource to provide a nontrivial default configuration.

Doug mentioned that it may also help simplify associated type inference (https://github.com/apple/swift/pull/13012#discussion_r152124535).

Oh, I thought this was something related to choosing a defaults for associated types, which might have helped with my current associated-type-inference quandary. The topic you actually wanted to discuss is disjoint (sorry).

I was wondering how it was related and wondered if it was somehow due to reduction in the size of the overload set. If you have any ideas on changes that might help guide the inference algorithm somehow please start a new thread. Even if you’re only able to describe the challenges it might be worth a thread if it’s possible others might have useful ideas. Improving the reliability and predictability of inference is a very important topic!

  - Doug

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

(Howard Lovatt) #19

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.

-- 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.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> 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 (https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug requested it be discussed on list is that I haven’t thought of a good way to express this syntactically. I am interested in hearing general feedback on the idea. I am also looking for syntax suggestions.

Matthew

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

(Xiaodi Wu) #20

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

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.

···

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:

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 (
https://github.com/apple/swift/pull/13012#discussion_r152124535).

The reason that I call this a pre-pitch and one reason Doug
requested it be discussed on list is that I haven’t thought of a good way
to express this syntactically. I am interested in hearing general feedback
on the idea. I am also looking for syntax suggestions.

Matthew

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