[Pitch] Move @noescape


(Chris Lattner) #1

Chris Eidhof noticed an emergent result of removing our currying syntax: it broke some useful code using @noescape, because we only allowed it on parameter declarations, not on general things-of-function-type. This meant that manually curried code like this:

func curriedFlatMap<A, B>(x: [A]) -> (@noescape A -> [B]) -> [B] {
    return { f in
        x.flatMap(f)
    }
}

Was rejected. Fixing this was straight-forward (https://github.com/apple/swift/commit/c3c6beac72bc0368030f06d52c46b6444fc48dbd), but required @noescape being allowed on arbitrary function types. Now that we have that, these two declarations are equivalent:

  func f(@noescape fn : () -> ()) {}
  func f(fn : @noescape () -> ()) {}

I propose that we remove the former syntax, migrating code to the later form. This leads to better consistency between our declarations and types, and follows the precedent of inout. @autoclosure should also probably move as well.

Thoughts?

-Chris


(Jacob Bandes-Storch) #2

Will this allow me to write "let myDispatchSync = dispatch_sync as!
@noescape () -> Void" ? :smiley:

Sounds good to me, the old syntax is not necessary anymore.

Jacob

···

On Thu, Mar 3, 2016 at 2:01 PM, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

Chris Eidhof noticed an emergent result of removing our currying syntax:
it broke some useful code using @noescape, because we only allowed it on
parameter declarations, not on general things-of-function-type. This meant
that manually curried code like this:

func curriedFlatMap<A, B>(x: [A]) -> (@noescape A -> [B]) -> [B] {
    return { f in
        x.flatMap(f)
    }
}

Was rejected. Fixing this was straight-forward (
https://github.com/apple/swift/commit/c3c6beac72bc0368030f06d52c46b6444fc48dbd),
but required @noescape being allowed on arbitrary function types. Now that
we have that, these two declarations are equivalent:

        func f(@noescape fn : () -> ()) {}
        func f(fn : @noescape () -> ()) {}

I propose that we remove the former syntax, migrating code to the later
form. This leads to better consistency between our declarations and types,
and follows the precedent of inout. @autoclosure should also probably move
as well.

Thoughts?

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


(Brent Royal-Gordon) #3

Chris Eidhof noticed an emergent result of removing our currying syntax: it broke some useful code using @noescape, because we only allowed it on parameter declarations, not on general things-of-function-type. This meant that manually curried code like this:

func curriedFlatMap<A, B>(x: [A]) -> (@noescape A -> [B]) -> [B] {
   return { f in
       x.flatMap(f)
   }
}

Was rejected. Fixing this was straight-forward (https://github.com/apple/swift/commit/c3c6beac72bc0368030f06d52c46b6444fc48dbd), but required @noescape being allowed on arbitrary function types. Now that we have that, these two declarations are equivalent:

  func f(@noescape fn : () -> ()) {}
  func f(fn : @noescape () -> ()) {}

I propose that we remove the former syntax, migrating code to the later form. This leads to better consistency between our declarations and types, and follows the precedent of inout. @autoclosure should also probably move as well.

Thoughts?

I suppose that makes sense.

While we're here, it actually occurred to me a while ago that `@noescape` could be applied to reference types as well as closures, indicating that the function doesn't keep them alive in the long run. Would that be useful information for the Swift compiler? I could imagine it helping the compiler to pin down the lifetimes of temporary objects, for instance.

···

--
Brent Royal-Gordon
Architechies


(Félix Cloutier) #4

Is @autoclosure type information? To me, it feels more like a parameter attribute, so I'm happy to have it on the parameter name side.

Félix

···

Le 3 mars 2016 à 17:01:41, Chris Lattner via swift-evolution <swift-evolution@swift.org> a écrit :

Chris Eidhof noticed an emergent result of removing our currying syntax: it broke some useful code using @noescape, because we only allowed it on parameter declarations, not on general things-of-function-type. This meant that manually curried code like this:

func curriedFlatMap<A, B>(x: [A]) -> (@noescape A -> [B]) -> [B] {
   return { f in
       x.flatMap(f)
   }
}

Was rejected. Fixing this was straight-forward (https://github.com/apple/swift/commit/c3c6beac72bc0368030f06d52c46b6444fc48dbd), but required @noescape being allowed on arbitrary function types. Now that we have that, these two declarations are equivalent:

  func f(@noescape fn : () -> ()) {}
  func f(fn : @noescape () -> ()) {}

I propose that we remove the former syntax, migrating code to the later form. This leads to better consistency between our declarations and types, and follows the precedent of inout. @autoclosure should also probably move as well.

Thoughts?

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


(Howard Lovatt) #5

Good idea. The annotation is associated with the type not with the name or
label.

···

On Friday, 4 March 2016, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

Chris Eidhof noticed an emergent result of removing our currying syntax:
it broke some useful code using @noescape, because we only allowed it on
parameter declarations, not on general things-of-function-type. This meant
that manually curried code like this:

func curriedFlatMap<A, B>(x: [A]) -> (@noescape A -> [B]) -> [B] {
    return { f in
        x.flatMap(f)
    }
}

Was rejected. Fixing this was straight-forward (
https://github.com/apple/swift/commit/c3c6beac72bc0368030f06d52c46b6444fc48dbd),
but required @noescape being allowed on arbitrary function types. Now that
we have that, these two declarations are equivalent:

        func f(@noescape fn : () -> ()) {}
        func f(fn : @noescape () -> ()) {}

I propose that we remove the former syntax, migrating code to the later
form. This leads to better consistency between our declarations and types,
and follows the precedent of inout. @autoclosure should also probably move
as well.

Thoughts?

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

--
-- Howard.


(Jacob Bandes-Storch) #6

My enthusiasm got the better of me... of course I meant (@noescape () ->
Void) -> Void.

···

On Thu, Mar 3, 2016 at 2:14 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

Will this allow me to write "let myDispatchSync = dispatch_sync as!
@noescape () -> Void" ? :smiley:

Sounds good to me, the old syntax is not necessary anymore.

Jacob

On Thu, Mar 3, 2016 at 2:01 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

Chris Eidhof noticed an emergent result of removing our currying syntax:
it broke some useful code using @noescape, because we only allowed it on
parameter declarations, not on general things-of-function-type. This meant
that manually curried code like this:

func curriedFlatMap<A, B>(x: [A]) -> (@noescape A -> [B]) -> [B] {
    return { f in
        x.flatMap(f)
    }
}

Was rejected. Fixing this was straight-forward (
https://github.com/apple/swift/commit/c3c6beac72bc0368030f06d52c46b6444fc48dbd),
but required @noescape being allowed on arbitrary function types. Now that
we have that, these two declarations are equivalent:

        func f(@noescape fn : () -> ()) {}
        func f(fn : @noescape () -> ()) {}

I propose that we remove the former syntax, migrating code to the later
form. This leads to better consistency between our declarations and types,
and follows the precedent of inout. @autoclosure should also probably move
as well.

Thoughts?

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


(Brent Royal-Gordon) #7

I suppose that makes sense.

Sorry to double-post, but I'm actually rethinking @autoclosure.

My feeling is that we should only attach information to the *type* if it follows the value around and if, at least in principle, it would make sense in at least some other type-like contexts. @noescape fits the bill; I can easily see the semantics and utility of this:

  let closure: @noescape Foo -> Bool = { $0.isBar }

But @autoclosure isn't the same way. It's concerned with the syntax used in the parameter list, not the semantics. Perhaps I would feel differently if it were an attribute that said "make this a computed variable bound to an expression which changes every time you set it":

  var currentDate: @expression NSDate = NSDate()
  if useServerDate {
    time = server.currentDate()
  }

But that's not what @autoclosure does; it takes an expression, wraps it in a closure, and drops it into an otherwise perfectly normal variable. That just doesn't feel like functionality that should be attached to a type.

···

--
Brent Royal-Gordon
Architechies


(Chris Lattner) #8

This proposal is merely a syntactic one, but there are a number of orthogonal functionality extensions that we could consider, e.g. allowing noescape members inside structs and enums, supporting noescape optional functions e.g.:

  func f(a : @noescape (()->())?) {…}

etc. These sorts of proposals are all interesting and useful, but need to be individually motivated, to make sure the complexity burden added by them is justified.

-Chris

···

On Mar 3, 2016, at 2:48 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

I propose that we remove the former syntax, migrating code to the later form. This leads to better consistency between our declarations and types, and follows the precedent of inout. @autoclosure should also probably move as well.

Thoughts?

I suppose that makes sense.

While we're here, it actually occurred to me a while ago that `@noescape` could be applied to reference types as well as closures, indicating that the function doesn't keep them alive in the long run. Would that be useful information for the Swift compiler? I could imagine it helping the compiler to pin down the lifetimes of temporary objects, for instance.


(Chris Lattner) #9

Will this allow me to write "let myDispatchSync = dispatch_sync as! @noescape () -> Void" ? :-D

Yes, but you need to spell it like this:

  let myDispatchSync = dispatch_sync as! ((dispatch_queue_t, @noescape @convention(block) () -> ()) -> Void)

The right answer here is to add @noescape to the imported symbol of course.

-Chris

···

On Mar 3, 2016, at 2:14 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

Sounds good to me, the old syntax is not necessary anymore.

Jacob

On Thu, Mar 3, 2016 at 2:01 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Chris Eidhof noticed an emergent result of removing our currying syntax: it broke some useful code using @noescape, because we only allowed it on parameter declarations, not on general things-of-function-type. This meant that manually curried code like this:

func curriedFlatMap<A, B>(x: [A]) -> (@noescape A -> [B]) -> [B] {
    return { f in
        x.flatMap(f)
    }
}

Was rejected. Fixing this was straight-forward (https://github.com/apple/swift/commit/c3c6beac72bc0368030f06d52c46b6444fc48dbd), but required @noescape being allowed on arbitrary function types. Now that we have that, these two declarations are equivalent:

        func f(@noescape fn : () -> ()) {}
        func f(fn : @noescape () -> ()) {}

I propose that we remove the former syntax, migrating code to the later form. This leads to better consistency between our declarations and types, and follows the precedent of inout. @autoclosure should also probably move as well.

Thoughts?

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


(Chris Lattner) #10

I think it does, and let me illustrate why. Consider an autoclosure-taking function like this (one silly example):

  func f(@autoclosure a : () -> ()) {}

You can use it as you’d expect, e.g.:

  f(print("hello”))

Of course, f is a first class value, and you can assign it:

  let x = f
  x(print("hello"))

This works, because x has type "(@autoclosure () -> ()) -> ()”. You can see this if you force a type error:

  let y : Int = x
  // error: cannot convert value of type '(@autoclosure () -> ()) -> ()' to specified type 'Int'

However, you can’t write this out explicitly:

  let x2 : (@autoclosure () -> ()) -> () = f
  // error: attribute can only be applied to declarations, not types

This seems dumb to me :-) you should be able to write the type for any declaration you can produce. Once you do that, it makes sense to spell the original function as:

  func f(a : @autoclosure () -> ()) {}

for consistency. Yes, I totally get the irony of the fact that @autoclosure used to be on the type in swift 1.

-Chris

···

On Mar 3, 2016, at 3:01 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

I suppose that makes sense.

Sorry to double-post, but I'm actually rethinking @autoclosure.

My feeling is that we should only attach information to the *type* if it follows the value around and if, at least in principle, it would make sense in at least some other type-like contexts.


(Chris Lattner) #11

Ah, while this syntactically parses, but the cast is considered to fail at runtime given that it is not a safe conversion. This is the domain of an unsafe cast.

-Chris

···

On Mar 3, 2016, at 4:10 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 3, 2016, at 2:14 PM, Jacob Bandes-Storch <jtbandes@gmail.com <mailto:jtbandes@gmail.com>> wrote:

Will this allow me to write "let myDispatchSync = dispatch_sync as! @noescape () -> Void" ? :-D

Yes, but you need to spell it like this:

  let myDispatchSync = dispatch_sync as! ((dispatch_queue_t, @noescape @convention(block) () -> ()) -> Void)


(Brent Royal-Gordon) #12

My feeling is that we should only attach information to the *type* if it follows the value around and if, at least in principle, it would make sense in at least some other type-like contexts.

I think it does, and let me illustrate why. Consider an autoclosure-taking function like this (one silly example):

  func f(@autoclosure a : () -> ()) {}

You can use it as you’d expect, e.g.:

  f(print("hello”))

Of course, f is a first class value, and you can assign it:

  let x = f
  x(print("hello"))

This works, because x has type "(@autoclosure () -> ()) -> ()”. You can see this if you force a type error:

  let y : Int = x
  // error: cannot convert value of type '(@autoclosure () -> ()) -> ()' to specified type 'Int'

However, you can’t write this out explicitly:

  let x2 : (@autoclosure () -> ()) -> () = f
  // error: attribute can only be applied to declarations, not types

This seems dumb to me :slight_smile: you should be able to write the type for any declaration you can produce. Once you do that, it makes sense to spell the original function as:

  func f(a : @autoclosure () -> ()) {}

for consistency. Yes, I totally get the irony of the fact that @autoclosure used to be on the type in swift 1.

How much sense does it really make to have a closure with an @autoclosure parameter, though? @autoclosure is meant to be syntactic sugar for when a function needs to control the evaluation of its parameters, like `&&` or `Result(try something())`. Does that feature make sense for closures? Even if it does, does it make sense for there to be a type error when you pass a `Foo -> Bar` where an `@autoclosure Foo -> Bar` is expected, or vice versa?

I think it might make more sense to think of your `f` as having the type `(() -> ()) -> Void` and have the @autoclosure-ness of particular parameters be something that's attached to `f` itself, not the function inside it. Just as a function's access level or @objc-ness isn't part of its type, nor is the @autoclosure-ness of its parameters.

Even if we decide we have to support @autoclosure on closures, if we turn parameter labels into a feature of the variable's name instead of its type (which I believe I've seen discussed), we will have an opportunity to specify that:

  let x2(@autoclosure _:): (() -> ()) -> () = f // no parameter label
  let x2(@autoclosure x:): (() -> ()) -> () = f // adding a parameter label

Or you could just shed the autoclosure-ness of the parameter by writing any of these:

  let x2 = f // inferring the type
  let x2: (() -> ()) -> () = f // stating it explicitly
  let x2(x:): (() -> ()) -> () = f // adding a parameter label

···

--
Brent Royal-Gordon
Architechies


(Chris Lattner) #13

This seems dumb to me :-) you should be able to write the type for any declaration you can produce. Once you do that, it makes sense to spell the original function as:

  func f(a : @autoclosure () -> ()) {}

for consistency. Yes, I totally get the irony of the fact that @autoclosure used to be on the type in swift 1.

How much sense does it really make to have a closure with an @autoclosure parameter, though? @autoclosure is meant to be syntactic sugar for when a function needs to control the evaluation of its parameters, like `&&` or `Result(try something())`. Does that feature make sense for closures?

I don’t think that it is wildly “widely useful”, but yes, it certainly makes sense. Not having it makes the language less orthogonal.

Even if it does, does it make sense for there to be a type error when you pass a `Foo -> Bar` where an `@autoclosure Foo -> Bar` is expected, or vice versa?

That is orthogonal to my proposal, but yes, these are different function types since they have different behaviors at the use site.

Even if we decide we have to support @autoclosure on closures, if we turn parameter labels into a feature of the variable's name instead of its type (which I believe I've seen discussed),

I don’t expect that to happen (because, e.g. that would fundamentally change how currying methods works), but if it does, we can certainly re-evaluate this.

-Chris

···

On Mar 4, 2016, at 9:32 PM, Brent Royal-Gordon <brent@architechies.com> wrote:


(Howard Lovatt) #14

Another point. I prefer lines to be ordered from most important bit of
information to least. That is why I prefer trailing ':' type information to
C-style declarations. Therefore my preference would be:

    func f(a: () -> () @autoclosure) {}

Since the name is the most important, the type the next most, and lastly
the annotation is the least important bit of information.

···

On Saturday, 5 March 2016, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

On Mar 4, 2016, at 9:32 PM, Brent Royal-Gordon <brent@architechies.com > <javascript:;>> wrote:
>> This seems dumb to me :slight_smile: you should be able to write the type for any
declaration you can produce. Once you do that, it makes sense to spell the
original function as:
>>
>> func f(a : @autoclosure () -> ()) {}
>>
>> for consistency. Yes, I totally get the irony of the fact that
@autoclosure used to be on the type in swift 1.
>
> How much sense does it really make to have a closure with an
@autoclosure parameter, though? @autoclosure is meant to be syntactic sugar
for when a function needs to control the evaluation of its parameters, like
`&&` or `Result(try something())`. Does that feature make sense for
closures?

I don’t think that it is wildly “widely useful”, but yes, it certainly
makes sense. Not having it makes the language less orthogonal.

> Even if it does, does it make sense for there to be a type error when
you pass a `Foo -> Bar` where an `@autoclosure Foo -> Bar` is expected, or
vice versa?

That is orthogonal to my proposal, but yes, these are different function
types since they have different behaviors at the use site.

> Even if we decide we have to support @autoclosure on closures, if we
turn parameter labels into a feature of the variable's name instead of its
type (which I believe I've seen discussed),

I don’t expect that to happen (because, e.g. that would fundamentally
change how currying methods works), but if it does, we can certainly
re-evaluate this.

-Chris

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

--
-- Howard.


(Jordan Rose) #15

This is problematic when there are multiple levels of closure:

fn: (Int) -> (Int) -> (Int, Int) @convention(block)
// without the attribute, equivalent to
fn: (Int) -> ((Int) -> (Int, Int))

Of course neither @noescape nor @autoclosure can apply to an arbitrary function value, but @convention can.

It's also inconsistent with all other attributes in the language. I see the idea of most-to-least important, but I don't think it actually results in a more readable syntax here.

Jordan

···

On Mar 5, 2016, at 12:43, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

Another point. I prefer lines to be ordered from most important bit of information to least. That is why I prefer trailing ':' type information to C-style declarations. Therefore my preference would be:

    func f(a: () -> () @autoclosure) {}

Since the name is the most important, the type the next most, and lastly the annotation is the least important bit of information.

On Saturday, 5 March 2016, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Mar 4, 2016, at 9:32 PM, Brent Royal-Gordon <brent@architechies.com <javascript:;>> wrote:
>> This seems dumb to me :slight_smile: you should be able to write the type for any declaration you can produce. Once you do that, it makes sense to spell the original function as:
>>
>> func f(a : @autoclosure () -> ()) {}
>>
>> for consistency. Yes, I totally get the irony of the fact that @autoclosure used to be on the type in swift 1.
>
> How much sense does it really make to have a closure with an @autoclosure parameter, though? @autoclosure is meant to be syntactic sugar for when a function needs to control the evaluation of its parameters, like `&&` or `Result(try something())`. Does that feature make sense for closures?

I don’t think that it is wildly “widely useful”, but yes, it certainly makes sense. Not having it makes the language less orthogonal.

> Even if it does, does it make sense for there to be a type error when you pass a `Foo -> Bar` where an `@autoclosure Foo -> Bar` is expected, or vice versa?

That is orthogonal to my proposal, but yes, these are different function types since they have different behaviors at the use site.

> Even if we decide we have to support @autoclosure on closures, if we turn parameter labels into a feature of the variable's name instead of its type (which I believe I've seen discussed),

I don’t expect that to happen (because, e.g. that would fundamentally change how currying methods works), but if it does, we can certainly re-evaluate this.

-Chris

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

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


(Howard Lovatt) #16

You could use brackets, e.g.:

fn: (Int) -> (Int) -> (Int, Int) @convention(block)

// Is the same as:

fn: (Int) -> ((Int) -> (Int, Int)) @convention(block)
// Which is not the same as:
fn: (Int) -> ((Int) -> (Int, Int) @convention(block))

  -- Howard.

···

On 9 March 2016 at 10:16, Jordan Rose <jordan_rose@apple.com> wrote:

This is problematic when there are multiple levels of closure:

fn: (Int) -> (Int) -> (Int, Int) @convention(block)

// without the attribute, equivalent to

fn: (Int) -> ((Int) -> (Int, Int))

Of course neither @noescape nor @autoclosure can apply to an arbitrary
function value, but @convention can.

It's also inconsistent with all other attributes in the language. I see
the idea of most-to-least important, but I don't think it actually results
in a more readable syntax here.

Jordan

On Mar 5, 2016, at 12:43, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:

Another point. I prefer lines to be ordered from most important bit of
information to least. That is why I prefer trailing ':' type information
to C-style declarations. Therefore my preference would be:

    func f(a: () -> () @autoclosure) {}

Since the name is the most important, the type the next most, and lastly
the annotation is the least important bit of information.

On Saturday, 5 March 2016, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

On Mar 4, 2016, at 9:32 PM, Brent Royal-Gordon <brent@architechies.com> >> wrote:
>> This seems dumb to me :slight_smile: you should be able to write the type for any
declaration you can produce. Once you do that, it makes sense to spell the
original function as:
>>
>> func f(a : @autoclosure () -> ()) {}
>>
>> for consistency. Yes, I totally get the irony of the fact that
@autoclosure used to be on the type in swift 1.
>
> How much sense does it really make to have a closure with an
@autoclosure parameter, though? @autoclosure is meant to be syntactic sugar
for when a function needs to control the evaluation of its parameters, like
`&&` or `Result(try something())`. Does that feature make sense for
closures?

I don’t think that it is wildly “widely useful”, but yes, it certainly
makes sense. Not having it makes the language less orthogonal.

> Even if it does, does it make sense for there to be a type error when
you pass a `Foo -> Bar` where an `@autoclosure Foo -> Bar` is expected, or
vice versa?

That is orthogonal to my proposal, but yes, these are different function
types since they have different behaviors at the use site.

> Even if we decide we have to support @autoclosure on closures, if we
turn parameter labels into a feature of the variable's name instead of its
type (which I believe I've seen discussed),

I don’t expect that to happen (because, e.g. that would fundamentally
change how currying methods works), but if it does, we can certainly
re-evaluate this.

-Chris

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

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