[Pitch] Extending [at]autoclosure


(Adrian Zubarev) #1

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

···

--
Adrian Zubarev
Sent with Airmail


(Gor Gyolchanyan) #2

I have thought of this before, but I always got stuck on the problem of nested closures.
Imagine you’re calling a function that takes an auto closure inside another closure.
What exactly will `$0` refer to?

···

On Jun 24, 2017, at 7:10 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

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


(Ben Rimmington) #3

Note that you can forward an autoclosure to another function:
Welcome to Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42).
  1> func foo(_ test: @autoclosure () -> Int) {
  2. print(#function, test())
  3. }
  4.
  5. func bar(_ test: @autoclosure () -> Int) {
  6. foo(test)
  7. }
  8.
  9. func baz(_ test: @autoclosure () -> Int) {
10. bar(test)
11. }
12.
13. baz(42)
foo 42
14>

···

On 24 Jun 2017, at 17:10, Adrian Zubarev wrote:

2. Make @autoclosure only wrap when necessary:

func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)


(Adrian Zubarev) #4

Well as Jordan Rose said on the linked SR, option (1) will probably never happen. Option (3) only makes sense if all of the options are supported (in that case there wouldn’t be any need for explicit @autoclosure, which could simply be merged into the closure type), or (2) is NOT supported so that one could pass a default autoclosure.

It leaves us only with (2), which is potentially a (small) breaking change, but it also feels more like a fix. I cannot imagine anyone is wrapping whole closures with auto closure, nor do I think a ‘convenience’ operation should disable the explicit ability to pass in a closure with the same signature. The latter feels like a bug. Furthermore I think most code that relies on this is already doing something like.

func bar(_ closure: @autoclosure () -> Int) { foo(closure)}

func foo(_ closure: () -> Int)
But this is only an assumption of mine.

Theoretically it suppose to work the other way around, right? Again @autoclosure supposed to be a syntactical convenience feature which implies that it won’t disable *too* much from the closure type. Disallowing arguments is logical consequence but not the other issues I mentioned here and in the SR.

···

One question: Do we need to go through a full evolution process for pitch (2) or is a bug report enough here?

--
Adrian Zubarev
Sent with Airmail

Am 30. Juni 2017 um 00:59:45, Beta (rwidmann@apple.com) schrieb:

These are all interesting ideas at first blush, but introduce some oddities into the type system

1. We accept this :flushed:. If we were to take this as an official language change it would mean that we would allow coercing T to (_) -> T by emitting a closure that takes an argument list (of arity given by the contextual type) that we throw away anyways. I would much prefer we diagnose this instead. @autoclosure is a syntactically convenient way to ask for laziness - that’s it.

2. Doing this collapses overloads on @autoclosure

func foo(_ f : @autoclosure () -> String) {}
func foo(_ f : () -> String) {}

Which is fine by me except for the code you would break that relies on this. I don’t see a reasonable migration path here - perhaps you have one in mind.

3. @autoclosure is a parameter attribute. Allowing it to appear in other positions is redundant and doesn’t actually accomplish anything outside of maintaining consistency with the first point.

I hope I don’t come off as too harsh. It’s just a little shocking to me that we accept the code in the linked SR.

~Robert Widmann

On Jun 24, 2017, at 9:10 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

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


(Adrian Zubarev) #5

Imagine you’re calling a function that takes an auto closure inside another closure.
I’ll try to restructure this sentence and hopefully understand it correctly:

A closure with a nested function that takes an autoclosure, which has at least one parameter.

At first some other examples:

let test1: ([String]) -> Void = {
    $0.forEach { print($0) /* local argument wins */ }
}

let test2: ([String]) -> Void = {
    $0.forEach { string in
        // error: anonymous closure arguments cannot be used inside
        // a closure that has explicit arguments print($0)
        print($0)
    }
}

let test3: ([String]) -> Void = { array in
    array.forEach { string in
        print(array) // fine again
    }
}
Now let’s showcase the issues you’ve mentioned:

func foo(_: @autoclosure (String) -> String) {}

let test4: ([String]) -> Void = {
    print($0)
     
    foo("\($0)") // Should be an error because it's ambiguous
}

// We have two options to resolve the issue:
let test4_1: ([String]) -> Void = { array in
    print(array)

    foo("\($0)") // Now it's fine
}

let test4_2: ([String]) -> Void = {
    print($0)
     
    // If closures would be allowed, so that `@autoclosure` will only
    // wrap when necessary, here the local shorthand argument will win
    // and behave like in `test1`
    foo({ "\($0)" })
     
    // Or in this example a trailing closure would do the same trick
    foo { "\($0)" }
}
I have an API where I would want to use @autoclosure that supports #1 and #2.

open func push(_ viewController: UIViewController,
               option: Option = .animated,
               with animator: (Transition) -> Animator = animator(for:)) { ... }
The caller could use a simple expression that will receive an important and necessary parameter to instantiate the animator.

containerViewController.push(someVC, with: CustomAnimator(for: $0 /* transition */))
But as already shown in the current implementation the closure should still be able to accept a default closure which takes some arguments.

···

--
Adrian Zubarev
Sent with Airmail

Am 25. Juni 2017 um 05:53:58, Gor Gyolchanyan (gor@gyolchanyan.com) schrieb:

I have thought of this before, but I always got stuck on the problem of nested closures.
Imagine you’re calling a function that takes an auto closure inside another closure.
What exactly will `$0` refer to?

On Jun 24, 2017, at 7:10 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

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


(Adrian Zubarev) #6

Hi Ben,

Is there any difference to this altered version of your example?

func foo(_ test: () -> Int) {
    print(#function, test())
}

func bar(_ test: () -> Int) {
    foo(test)
}

func baz(_ test: @autoclosure () -> Int) {
    bar(test)
}

baz(42)
It looks exactly the same to me. After the pitched change one could finally pass a normal closure with the same signature to a function that takes an autoclosure, which then won’t wrap.

In the following example @autoclosure wins over a concrete closure type and produces a false result, because it will wrap () -> Void into () -> (() -> Void). I kinda have a feeling that the proposed change (#2) will resolve this issue, but I don’t have the insights on what @autoclosure does exactly to the closure type.

extension Bool {

    /// #1
    func whenTrue(execute closure: () -> Void) {
        if self { closure() }
    }

    /// #2
    func whenTrue(execute closure: @autoclosure () -> Void) {
        if self { closure() }
    }

    /// #3
    func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
        if self { return closure() }
        return nil
    }
}

let test: () -> Void = { }

true.whenTrue(execute: test) // #3 wins, but I expect #1 here
Ideally I would want a single function that handles every case:

// Does not wrap when a closure `() -> T` is passed to it, because of #2
@discardableResult
func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
    if self { return closure() }
    return nil
}

···

--
Adrian Zubarev
Sent with Airmail

Am 25. Juni 2017 um 09:03:09, Ben Rimmington (me@benrimmington.com) schrieb:

On 24 Jun 2017, at 17:10, Adrian Zubarev wrote:

2. Make @autoclosure only wrap when necessary:

func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

Note that you can forward an autoclosure to another function:
Welcome to Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42).
  1> func foo(_ test: @autoclosure () -> Int) {
  2. print(#function, test())
  3. }
  4.
  5. func bar(_ test: @autoclosure () -> Int) {
  6. foo(test)
  7. }
  8.
  9. func baz(_ test: @autoclosure () -> Int) {
10. bar(test)
11. }
12.
13. baz(42)
foo 42
14>


(Jaden Geller) #7

Well as Jordan Rose said on the linked SR, option (1) will probably never happen. Option (3) only makes sense if all of the options are supported (in that case there wouldn’t be any need for explicit @autoclosure, which could simply be merged into the closure type), or (2) is NOT supported so that one could pass a default autoclosure.

It leaves us only with (2), which is potentially a (small) breaking change, but it also feels more like a fix. I cannot imagine anyone is wrapping whole closures with auto closure, nor do I think a ‘convenience’ operation should disable the explicit ability to pass in a closure with the same signature. The latter feels like a bug. Furthermore I think most code that relies on this is already doing something like.

func bar(_ closure: @autoclosure () -> Int) { foo(closure)}

func foo(_ closure: () -> Int)
But this is only an assumption of mine.

Theoretically it suppose to work the other way around, right? Again @autoclosure supposed to be a syntactical convenience feature which implies that it won’t disable *too* much from the closure type. Disallowing arguments is logical consequence but not the other issues I mentioned here and in the SR.

One question: Do we need to go through a full evolution process for pitch (2) or is a bug report enough here?

Surely the former—I'm fully against this change, and imagine others are also. Autoclosure exists to provide opt-in lazy evaluation of values by wrapping them in a closure. I think it's semantically incorrect to accept an already wrapped value here, and adding this sort of implicit conversion can introduce potential ambiguity when used with generic functions.

Very large -1.

···

On Jun 30, 2017, at 1:48 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

--
Adrian Zubarev
Sent with Airmail

Am 30. Juni 2017 um 00:59:45, Beta (rwidmann@apple.com) schrieb:

These are all interesting ideas at first blush, but introduce some oddities into the type system

1. We accept this :flushed:. If we were to take this as an official language change it would mean that we would allow coercing T to (_) -> T by emitting a closure that takes an argument list (of arity given by the contextual type) that we throw away anyways. I would much prefer we diagnose this instead. @autoclosure is a syntactically convenient way to ask for laziness - that’s it.

2. Doing this collapses overloads on @autoclosure

func foo(_ f : @autoclosure () -> String) {}
func foo(_ f : () -> String) {}

Which is fine by me except for the code you would break that relies on this. I don’t see a reasonable migration path here - perhaps you have one in mind.

3. @autoclosure is a parameter attribute. Allowing it to appear in other positions is redundant and doesn’t actually accomplish anything outside of maintaining consistency with the first point.

I hope I don’t come off as too harsh. It’s just a little shocking to me that we accept the code in the linked SR.

~Robert Widmann

On Jun 24, 2017, at 9:10 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
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


(Adrian Zubarev) #8

I clearly disagree with your point. Autoclosure supposed to be a syntactically convenience feature to omit braces, which as a consequence needs to disable arguments. However it is not said that you cannot pass a closure with the same signature to the autoclosure, which currently is not possible unless it’s another autoclosure. This doesn’t feel right at all.

func foo(_: @autoclosure () -> Void) {}

func bar(_ test: @autoclosure () -> Void) {
   foo(test) // works
}

let closure: () -> Void = {}

foo(closure) // error
Here is another example where autoclosure takes over and produces false result even when the correct overload is present but the resolution ends up picking an autoclosure.

extension Bool {

    /// #1
    func whenTrue(execute closure: () -> Void) {
        if self { closure() }
    }

    /// #2
    func whenTrue(execute closure: @autoclosure () -> Void) {
        if self { closure() }
    }

    /// #3
    func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
        if self { return closure() }
        return nil
    }
}

let test: () -> Void = { }
// #3 wins and produces a wrong type () -> (() -> Void)?, but I expect #1 here
// () -> Void?
true.whenTrue(execute: test)
A syntactical convenience feature should not disable explicitness!

···

--
Adrian Zubarev
Sent with Airmail

Am 1. Juli 2017 um 19:46:55, jaden.geller@gmail.com (jaden.geller@gmail.com) schrieb:

On Jun 30, 2017, at 1:48 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Well as Jordan Rose said on the linked SR, option (1) will probably never happen. Option (3) only makes sense if all of the options are supported (in that case there wouldn’t be any need for explicit @autoclosure, which could simply be merged into the closure type), or (2) is NOT supported so that one could pass a default autoclosure.

It leaves us only with (2), which is potentially a (small) breaking change, but it also feels more like a fix. I cannot imagine anyone is wrapping whole closures with auto closure, nor do I think a ‘convenience’ operation should disable the explicit ability to pass in a closure with the same signature. The latter feels like a bug. Furthermore I think most code that relies on this is already doing something like.

func bar(_ closure: @autoclosure () -> Int) { foo(closure)}

func foo(_ closure: () -> Int)
But this is only an assumption of mine.

Theoretically it suppose to work the other way around, right? Again @autoclosure supposed to be a syntactical convenience feature which implies that it won’t disable *too* much from the closure type. Disallowing arguments is logical consequence but not the other issues I mentioned here and in the SR.

One question: Do we need to go through a full evolution process for pitch (2) or is a bug report enough here?

Surely the former—I'm fully against this change, and imagine others are also. Autoclosure exists to provide opt-in lazy evaluation of values by wrapping them in a closure. I think it's semantically incorrect to accept an already wrapped value here, and adding this sort of implicit conversion can introduce potential ambiguity when used with generic functions.

Very large -1.

--
Adrian Zubarev
Sent with Airmail

Am 30. Juni 2017 um 00:59:45, Beta (rwidmann@apple.com) schrieb:

These are all interesting ideas at first blush, but introduce some oddities into the type system

1. We accept this :flushed:. If we were to take this as an official language change it would mean that we would allow coercing T to (_) -> T by emitting a closure that takes an argument list (of arity given by the contextual type) that we throw away anyways. I would much prefer we diagnose this instead. @autoclosure is a syntactically convenient way to ask for laziness - that’s it.

2. Doing this collapses overloads on @autoclosure

func foo(_ f : @autoclosure () -> String) {}
func foo(_ f : () -> String) {}

Which is fine by me except for the code you would break that relies on this. I don’t see a reasonable migration path here - perhaps you have one in mind.

3. @autoclosure is a parameter attribute. Allowing it to appear in other positions is redundant and doesn’t actually accomplish anything outside of maintaining consistency with the first point.

I hope I don’t come off as too harsh. It’s just a little shocking to me that we accept the code in the linked SR.

~Robert Widmann

On Jun 24, 2017, at 9:10 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
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


(Jaden Geller) #9

I feel strongly that you shouldn’t be using autoclosure in these cases. Instead, write `true.whenTrue { … }` and `true.whenTrue(myClosure)`.

···

On Jul 1, 2017, at 3:17 PM, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

I clearly disagree with your point. Autoclosure supposed to be a syntactically convenience feature to omit braces, which as a consequence needs to disable arguments. However it is not said that you cannot pass a closure with the same signature to the autoclosure, which currently is not possible unless it’s another autoclosure. This doesn’t feel right at all.

func foo(_: @autoclosure () -> Void) {}

func bar(_ test: @autoclosure () -> Void) {
   foo(test) // works
}

let closure: () -> Void = {}

foo(closure) // error
Here is another example where autoclosure takes over and produces false result even when the correct overload is present but the resolution ends up picking an autoclosure.

extension Bool {

    /// #1
    func whenTrue(execute closure: () -> Void) {
        if self { closure() }
    }

    /// #2
    func whenTrue(execute closure: @autoclosure () -> Void) {
        if self { closure() }
    }

    /// #3
    func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
        if self { return closure() }
        return nil
    }
}

let test: () -> Void = { }
// #3 wins and produces a wrong type () -> (() -> Void)?, but I expect #1 here
// () -> Void?
true.whenTrue(execute: test)
A syntactical convenience feature should not disable explicitness!

--
Adrian Zubarev
Sent with Airmail

Am 1. Juli 2017 um 19:46:55, jaden.geller@gmail.com <mailto:jaden.geller@gmail.com> (jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>) schrieb:

On Jun 30, 2017, at 1:48 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Well as Jordan Rose said on the linked SR, option (1) will probably never happen. Option (3) only makes sense if all of the options are supported (in that case there wouldn’t be any need for explicit @autoclosure, which could simply be merged into the closure type), or (2) is NOT supported so that one could pass a default autoclosure.

It leaves us only with (2), which is potentially a (small) breaking change, but it also feels more like a fix. I cannot imagine anyone is wrapping whole closures with auto closure, nor do I think a ‘convenience’ operation should disable the explicit ability to pass in a closure with the same signature. The latter feels like a bug. Furthermore I think most code that relies on this is already doing something like.

func bar(_ closure: @autoclosure () -> Int) { foo(closure)}

func foo(_ closure: () -> Int)
But this is only an assumption of mine.

Theoretically it suppose to work the other way around, right? Again @autoclosure supposed to be a syntactical convenience feature which implies that it won’t disable *too* much from the closure type. Disallowing arguments is logical consequence but not the other issues I mentioned here and in the SR.

One question: Do we need to go through a full evolution process for pitch (2) or is a bug report enough here?

Surely the former—I'm fully against this change, and imagine others are also. Autoclosure exists to provide opt-in lazy evaluation of values by wrapping them in a closure. I think it's semantically incorrect to accept an already wrapped value here, and adding this sort of implicit conversion can introduce potential ambiguity when used with generic functions.

Very large -1.

--
Adrian Zubarev
Sent with Airmail

Am 30. Juni 2017 um 00:59:45, Beta (rwidmann@apple.com <mailto:rwidmann@apple.com>) schrieb:

These are all interesting ideas at first blush, but introduce some oddities into the type system

1. We accept this :flushed:. If we were to take this as an official language change it would mean that we would allow coercing T to (_) -> T by emitting a closure that takes an argument list (of arity given by the contextual type) that we throw away anyways. I would much prefer we diagnose this instead. @autoclosure is a syntactically convenient way to ask for laziness - that’s it.

2. Doing this collapses overloads on @autoclosure

func foo(_ f : @autoclosure () -> String) {}
func foo(_ f : () -> String) {}

Which is fine by me except for the code you would break that relies on this. I don’t see a reasonable migration path here - perhaps you have one in mind.

3. @autoclosure is a parameter attribute. Allowing it to appear in other positions is redundant and doesn’t actually accomplish anything outside of maintaining consistency with the first point.

I hope I don’t come off as too harsh. It’s just a little shocking to me that we accept the code in the linked SR.

~Robert Widmann

On Jun 24, 2017, at 9:10 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
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


(Adrian Zubarev) #10

It’s not about whether I should use these like this or not. It’s about that you can pass an autoclosure to an autoclosure (where the metatype is the same as a normal closure), but you cannot pass a normal closure to an autoclosure. @escaping making things even worse. When using Void as a return type Xcode will provide you an additional completion option which only will result in an error!

func foo(_: @autoclosure () -> Void) { }

func bar(_ test: @autoclosure () -> Void) {
    foo(test) // works
}

func baz(_ test: @autoclosure @escaping () -> Void) {
    print(type(of: test)) // prints `() -> ()`
    foo(test) // error because it's `@escaping`
}

let closure: () -> Void = {}

bar(())

bar(closure) // error

// Suggested autocompletion by Xcode which results in an error
bar {
    <#code#>
}
Long story short autoclosure is bugged and deserves this fix.

Here a few examples where I’m using the mentioned non-generic extension instead of an if statement:

self.shouldPop.whenTrue(execute: self.toView.isUserInteractionEnabled = false)

($0 == .end).whenTrue(execute: completion)
(!option.isInteractive).whenTrue(execute: sendEvents)
There are other cases where I’d use it, but I cannot because the generic autoclosure function simply does not work as I’d expect it to work:

@discardableReuslt
func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
   if self { return closure() }
   return nil
}

···

--
Adrian Zubarev
Sent with Airmail

Am 2. Juli 2017 um 00:23:43, Jaden Geller (jaden.geller@gmail.com) schrieb:

I feel strongly that you shouldn’t be using autoclosure in these cases. Instead, write `true.whenTrue { … }` and `true.whenTrue(myClosure)`.

On Jul 1, 2017, at 3:17 PM, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

I clearly disagree with your point. Autoclosure supposed to be a syntactically convenience feature to omit braces, which as a consequence needs to disable arguments. However it is not said that you cannot pass a closure with the same signature to the autoclosure, which currently is not possible unless it’s another autoclosure. This doesn’t feel right at all.

func foo(_: @autoclosure () -> Void) {}

func bar(_ test: @autoclosure () -> Void) {
   foo(test) // works
}

let closure: () -> Void = {}

foo(closure) // error
Here is another example where autoclosure takes over and produces false result even when the correct overload is present but the resolution ends up picking an autoclosure.

extension Bool {

    /// #1
    func whenTrue(execute closure: () -> Void) {
        if self { closure() }
    }

    /// #2
    func whenTrue(execute closure: @autoclosure () -> Void) {
        if self { closure() }
    }

    /// #3
    func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
        if self { return closure() }
        return nil
    }
}

let test: () -> Void = { }
// #3 wins and produces a wrong type () -> (() -> Void)?, but I expect #1 here
// () -> Void?
true.whenTrue(execute: test)
A syntactical convenience feature should not disable explicitness!

--
Adrian Zubarev
Sent with Airmail

Am 1. Juli 2017 um 19:46:55, jaden.geller@gmail.com (jaden.geller@gmail.com) schrieb:

On Jun 30, 2017, at 1:48 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Well as Jordan Rose said on the linked SR, option (1) will probably never happen. Option (3) only makes sense if all of the options are supported (in that case there wouldn’t be any need for explicit @autoclosure, which could simply be merged into the closure type), or (2) is NOT supported so that one could pass a default autoclosure.

It leaves us only with (2), which is potentially a (small) breaking change, but it also feels more like a fix. I cannot imagine anyone is wrapping whole closures with auto closure, nor do I think a ‘convenience’ operation should disable the explicit ability to pass in a closure with the same signature. The latter feels like a bug. Furthermore I think most code that relies on this is already doing something like.

func bar(_ closure: @autoclosure () -> Int) { foo(closure)}

func foo(_ closure: () -> Int)
But this is only an assumption of mine.

Theoretically it suppose to work the other way around, right? Again @autoclosure supposed to be a syntactical convenience feature which implies that it won’t disable *too* much from the closure type. Disallowing arguments is logical consequence but not the other issues I mentioned here and in the SR.

One question: Do we need to go through a full evolution process for pitch (2) or is a bug report enough here?

Surely the former—I'm fully against this change, and imagine others are also. Autoclosure exists to provide opt-in lazy evaluation of values by wrapping them in a closure. I think it's semantically incorrect to accept an already wrapped value here, and adding this sort of implicit conversion can introduce potential ambiguity when used with generic functions.

Very large -1.

--
Adrian Zubarev
Sent with Airmail

Am 30. Juni 2017 um 00:59:45, Beta (rwidmann@apple.com) schrieb:

These are all interesting ideas at first blush, but introduce some oddities into the type system

1. We accept this :flushed:. If we were to take this as an official language change it would mean that we would allow coercing T to (_) -> T by emitting a closure that takes an argument list (of arity given by the contextual type) that we throw away anyways. I would much prefer we diagnose this instead. @autoclosure is a syntactically convenient way to ask for laziness - that’s it.

2. Doing this collapses overloads on @autoclosure

func foo(_ f : @autoclosure () -> String) {}
func foo(_ f : () -> String) {}

Which is fine by me except for the code you would break that relies on this. I don’t see a reasonable migration path here - perhaps you have one in mind.

3. @autoclosure is a parameter attribute. Allowing it to appear in other positions is redundant and doesn’t actually accomplish anything outside of maintaining consistency with the first point.

I hope I don’t come off as too harsh. It’s just a little shocking to me that we accept the code in the linked SR.

~Robert Widmann

On Jun 24, 2017, at 9:10 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
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


(Jaden Geller) #11

"you can pass an autoclosure to an autoclosure”

This is very surprising to me! I think that should be an error… When I write an argument of type `@autoclosure () -> T`, only arguments of type `T` ought to be accepted.

···

On Jul 1, 2017, at 3:50 PM, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

It’s not about whether I should use these like this or not. It’s about that you can pass an autoclosure to an autoclosure (where the metatype is the same as a normal closure), but you cannot pass a normal closure to an autoclosure. @escaping making things even worse. When using Void as a return type Xcode will provide you an additional completion option which only will result in an error!

func foo(_: @autoclosure () -> Void) { }

func bar(_ test: @autoclosure () -> Void) {
    foo(test) // works
}

func baz(_ test: @autoclosure @escaping () -> Void) {
    print(type(of: test)) // prints `() -> ()`
    foo(test) // error because it's `@escaping`
}

let closure: () -> Void = {}

bar(())

bar(closure) // error

// Suggested autocompletion by Xcode which results in an error
bar {
    <#code#>
}
Long story short autoclosure is bugged and deserves this fix.

Here a few examples where I’m using the mentioned non-generic extension instead of an if statement:

self.shouldPop.whenTrue(execute: self.toView.isUserInteractionEnabled = false)

($0 == .end).whenTrue(execute: completion)
(!option.isInteractive).whenTrue(execute: sendEvents)
There are other cases where I’d use it, but I cannot because the generic autoclosure function simply does not work as I’d expect it to work:

@discardableReuslt
func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
   if self { return closure() }
   return nil
}

--
Adrian Zubarev
Sent with Airmail

Am 2. Juli 2017 um 00:23:43, Jaden Geller (jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>) schrieb:

I feel strongly that you shouldn’t be using autoclosure in these cases. Instead, write `true.whenTrue { … }` and `true.whenTrue(myClosure)`.

On Jul 1, 2017, at 3:17 PM, Adrian Zubarev <adrian.zubarev@devandartist.com <mailto:adrian.zubarev@devandartist.com>> wrote:

I clearly disagree with your point. Autoclosure supposed to be a syntactically convenience feature to omit braces, which as a consequence needs to disable arguments. However it is not said that you cannot pass a closure with the same signature to the autoclosure, which currently is not possible unless it’s another autoclosure. This doesn’t feel right at all.

func foo(_: @autoclosure () -> Void) {}

func bar(_ test: @autoclosure () -> Void) {
   foo(test) // works
}

let closure: () -> Void = {}

foo(closure) // error
Here is another example where autoclosure takes over and produces false result even when the correct overload is present but the resolution ends up picking an autoclosure.

extension Bool {

    /// #1
    func whenTrue(execute closure: () -> Void) {
        if self { closure() }
    }

    /// #2
    func whenTrue(execute closure: @autoclosure () -> Void) {
        if self { closure() }
    }

    /// #3
    func whenTrue<T>(execute closure: @autoclosure () -> T) -> T? {
        if self { return closure() }
        return nil
    }
}

let test: () -> Void = { }
// #3 wins and produces a wrong type () -> (() -> Void)?, but I expect #1 here
// () -> Void?
true.whenTrue(execute: test)
A syntactical convenience feature should not disable explicitness!

--
Adrian Zubarev
Sent with Airmail

Am 1. Juli 2017 um 19:46:55, jaden.geller@gmail.com <mailto:jaden.geller@gmail.com> (jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>) schrieb:

On Jun 30, 2017, at 1:48 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Well as Jordan Rose said on the linked SR, option (1) will probably never happen. Option (3) only makes sense if all of the options are supported (in that case there wouldn’t be any need for explicit @autoclosure, which could simply be merged into the closure type), or (2) is NOT supported so that one could pass a default autoclosure.

It leaves us only with (2), which is potentially a (small) breaking change, but it also feels more like a fix. I cannot imagine anyone is wrapping whole closures with auto closure, nor do I think a ‘convenience’ operation should disable the explicit ability to pass in a closure with the same signature. The latter feels like a bug. Furthermore I think most code that relies on this is already doing something like.

func bar(_ closure: @autoclosure () -> Int) { foo(closure)}

func foo(_ closure: () -> Int)
But this is only an assumption of mine.

Theoretically it suppose to work the other way around, right? Again @autoclosure supposed to be a syntactical convenience feature which implies that it won’t disable *too* much from the closure type. Disallowing arguments is logical consequence but not the other issues I mentioned here and in the SR.

One question: Do we need to go through a full evolution process for pitch (2) or is a bug report enough here?

Surely the former—I'm fully against this change, and imagine others are also. Autoclosure exists to provide opt-in lazy evaluation of values by wrapping them in a closure. I think it's semantically incorrect to accept an already wrapped value here, and adding this sort of implicit conversion can introduce potential ambiguity when used with generic functions.

Very large -1.

--
Adrian Zubarev
Sent with Airmail

Am 30. Juni 2017 um 00:59:45, Beta (rwidmann@apple.com <mailto:rwidmann@apple.com>) schrieb:

These are all interesting ideas at first blush, but introduce some oddities into the type system

1. We accept this :flushed:. If we were to take this as an official language change it would mean that we would allow coercing T to (_) -> T by emitting a closure that takes an argument list (of arity given by the contextual type) that we throw away anyways. I would much prefer we diagnose this instead. @autoclosure is a syntactically convenient way to ask for laziness - that’s it.

2. Doing this collapses overloads on @autoclosure

func foo(_ f : @autoclosure () -> String) {}
func foo(_ f : () -> String) {}

Which is fine by me except for the code you would break that relies on this. I don’t see a reasonable migration path here - perhaps you have one in mind.

3. @autoclosure is a parameter attribute. Allowing it to appear in other positions is redundant and doesn’t actually accomplish anything outside of maintaining consistency with the first point.

I hope I don’t come off as too harsh. It’s just a little shocking to me that we accept the code in the linked SR.

~Robert Widmann

On Jun 24, 2017, at 9:10 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello folks,

Here is a quick and straightforward pitch about @autoclosure. Currently the attribute indicates that the caller has to pass an expression so that the braces can be omitted. This is a convenient behavior only, but it also has it’s shortcomings.

I would like to propose an extension of that behavior.

1. Allow access to arguments and shorthand argument names:
// Bug: https://bugs.swift.org/browse/SR-5296
func foo(_ test: @autoclosure (Int) -> Int = { $0 }) {
    print(test(42))
}

// Convenient access using shorthand arguments
foo(Int(Double($0) * 3.14)))

2. Make @autoclosure only wrap when necessary:
func bar(_ test: @autoclosure () -> Int) {
    print(test())
}

let test = { 42 }

// function produces expected type 'Int'; did you mean to call it with '()'?
bar(test)

3. Extend @autoclosure to closure types in general (this change is for consistent alignment):
// Note how we're using the shorthand argument list for this expression
let uppercaseWrapper: @autoclosure (String) -> String = $0.uppercased()

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
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