Making capturing semantics of local

FTM, local functions do not help with "keep functions short" and "keep
indentation level small" rules of thumb.

what are the actual benefits of local vs non local functions? locality
principle? (define something where it is used). but we do not have it in
much more important cases, say, with static variables - we have to declare
them outside of functions. ditto for normal variables in extensions - no
matter how carefully i group functions in individual extensions based on
their purpose -- potentially in different files -- it all breaks once i
need to add a single variable to that group which i have to to in the class
itself.

what would be the damage if we remove local functions altogether, would we
lose anything really useful? aside from the fact that "it is too late at
this stage" i mean (which is appreciated).

Mike

···

On Thu, 26 Oct 2017 21:54:37 -0700 Slava Pestov <spestov@apple.com> wrote:

Closures cannot replace all uses of local functions. Local functions can
be recursive, and have a generic parameter list.

Closures cannot replace all uses of local functions. Local functions can be recursive, and have a generic parameter list.

FTM, local functions do not help with "keep functions short" and "keep indentation level small" rules of thumb.

So do structs and classes :wink:

what are the actual benefits of local vs non local functions? locality principle? (define something where it is used). but we do not have it in much more important cases, say, with static variables - we have to declare them outside of functions. ditto for normal variables in extensions - no matter how carefully i group functions in individual extensions based on their purpose -- potentially in different files -- it all breaks once i need to add a single variable to that group which i have to to in the class itself.

Local functions can capture bindings from the outer function, just like methods can capture ‘self’ from the outer type. I don’t think a function with local functions is any less readable than a type declaration with methods. In many cases you can use both to solve the same problem, and the functional approach can be simpler.

what would be the damage if we remove local functions altogether, would we lose anything really useful? aside from the fact that "it is too late at this stage" i mean (which is appreciated).

I mean, we could remove a lot of language features without giving up turing completeness. But I’ve personally used local functions in multiple languages and found them useful. I certainly don’t see why the feature is actively harmful, which is the criteria for introducing a source breaking change in Swift 5.

Slava

···

On Oct 27, 2017, at 10:51 AM, Mike Kluev <mike.kluev@gmail.com> wrote:
On Thu, 26 Oct 2017 21:54:37 -0700 Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

Mike

I have run into nondescript compiler crashes (like segmentation faults, etc.) when using local functions to do certain things, like wrapping the parent function’s escaping closure arguments in other closures that capture variables from the parent function’s local scope, especially when those variables themselves are function types that do the wrapping, and therefore take closures as arguments. (Of course, all of these closures taking generic arguments conforming to protocols with associated types made things extra interesting.)

I don’t know if this makes local functions actively harmful, or if it means function types in capture lists need to support @escaping, but it does remind me to go back and try to reproduce those weird issues in a sample project or playground page so I can make a bug report to Apple.

Jonathan

···

On Oct 27, 2017, at 12:29, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

I mean, we could remove a lot of language features without giving up turing completeness. But I’ve personally used local functions in multiple languages and found them useful. I certainly don’t see why the feature is actively harmful, which is the criteria for introducing a source breaking change in Swift 5.

That sounds like a bug, and it could occur with closure expressions also, since at the SILGen level and below they’re basically the same thing. Please file a bug if you come up with a reduced test case.

Slava

···

On Oct 27, 2017, at 4:00 PM, Jon Gilbert <swiftevolution@jongilbert.com> wrote:

I have run into nondescript compiler crashes (like segmentation faults, etc.) when using local functions to do certain things, like wrapping the parent function’s escaping closure arguments in other closures that capture variables from the parent function’s local scope, especially when those variables themselves are function types that do the wrapping, and therefore take closures as arguments. (Of course, all of these closures taking generic arguments conforming to protocols with associated types made things extra interesting.)

I don’t know if this makes local functions actively harmful, or if it means function types in capture lists need to support @escaping, but it does remind me to go back and try to reproduce those weird issues in a sample project or playground page so I can make a bug report to Apple.

Jonathan

On Oct 27, 2017, at 12:29, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

I mean, we could remove a lot of language features without giving up turing completeness. But I’ve personally used local functions in multiple languages and found them useful. I certainly don’t see why the feature is actively harmful, which is the criteria for introducing a source breaking change in Swift 5.

I should qualify that I am not proposing removing local functions without
replacing them with closures that have the same power. I don't believe
powerful closures are something the compiler couldn't do, in fact I believe
it is something that is relatively easy. I believe this because there isn't
much difference between a function and a closure and because the function
behaviour is easy to fake and therefore something the compiler could do (in
fact do better than you can fake). Two of the example presented of things
closures can't do are mutual recursion and generics, both are fakable:

var isEven: ((UInt) -> Bool)! = nil // Forward declaration!
var isOdd: ((UInt) -> Bool)! = nil
isEven = { n in
    if n == 0 {
        return true
    } else {
        return isOdd(n - 1)
    }
}
isOdd = { n in
    if n == 0 {
        return false;
    } else {
        return isEven(n - 1)
    }
}
isEven(4) // True
isOdd(4) // False

struct GenericIncrement<T: Numeric> {
    let increment = { (n: T) -> T in
        n + 1
    }
}
let int = GenericIncrement<Int>() // Reify increment and name mangle!
int.increment(1) // 2
let double = GenericIncrement<Double>()
double.increment(1.1) // 2.1

The reason for showing code above is to demonstrate that the compiler could
do this (in fact it could do better) - I am not suggesting anyone uses the
above!

  -- Howard.

···

On 28 October 2017 at 10:01, Slava Pestov via swift-evolution < swift-evolution@swift.org> wrote:

That sounds like a bug, and it could occur with closure expressions also,
since at the SILGen level and below they’re basically the same thing.
Please file a bug if you come up with a reduced test case.

Slava

> On Oct 27, 2017, at 4:00 PM, Jon Gilbert <swiftevolution@jongilbert.com> > wrote:
>
> I have run into nondescript compiler crashes (like segmentation faults,
etc.) when using local functions to do certain things, like wrapping the
parent function’s escaping closure arguments in other closures that capture
variables from the parent function’s local scope, especially when those
variables themselves are function types that do the wrapping, and therefore
take closures as arguments. (Of course, all of these closures taking
generic arguments conforming to protocols with associated types made things
extra interesting.)
>
> I don’t know if this makes local functions actively harmful, or if it
means function types in capture lists need to support @escaping, but it
does remind me to go back and try to reproduce those weird issues in a
sample project or playground page so I can make a bug report to Apple.
>
> Jonathan
>
>> On Oct 27, 2017, at 12:29, Slava Pestov via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>> I mean, we could remove a lot of language features without giving up
turing completeness. But I’ve personally used local functions in multiple
languages and found them useful. I certainly don’t see why the feature is
actively harmful, which is the criteria for introducing a source breaking
change in Swift 5.
>

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

indeed:

let closure = {}
print("closure is \(closure)") // prints: "closure is (Function)"

QED

Mike

···

On 28 October 2017 at 00:00, Slava Pestov <spestov@apple.com> wrote:

That sounds like a bug, and it could occur with closure expressions also,
since at the SILGen level and below they’re basically the same thing.

I should qualify that I am not proposing removing local functions without replacing them with closures that have the same power.

If we can unify the two syntaxes, maybe. But even if that was possible, I’m leery because now named local functions will look quite different from local functions today:

let myLocalFn = { (x: Int, y: Int) in x + y }

vs

func myLocalFn(x: Int, y: Int) { return x + y }

Is that really an improvement? It looks different from non-local functions.

I don't believe powerful closures are something the compiler couldn't do, in fact I believe it is something that is relatively easy. I believe this because there isn't much difference between a function and a closure and because the function behaviour is easy to fake and therefore something the compiler could do (in fact do better than you can fake). Two of the example presented of things closures can't do are mutual recursion and generics, both are fakable:

var isEven: ((UInt) -> Bool)! = nil // Forward declaration!
var isOdd: ((UInt) -> Bool)! = nil
isEven = { n in
    if n == 0 {
        return true
    } else {
        return isOdd(n - 1)
    }
}
isOdd = { n in
    if n == 0 {
        return false;
    } else {
        return isEven(n - 1)
    }
}
isEven(4) // True
isOdd(4) // False

Ok, but again, how is this better than local functions?

struct GenericIncrement<T: Numeric> {
    let increment = { (n: T) -> T in
        n + 1
    }
}
let int = GenericIncrement<Int>() // Reify increment and name mangle!
int.increment(1) // 2
let double = GenericIncrement<Double>()
double.increment(1.1) // 2.1

Local types cannot capture values from outer scopes right now, so this does not give you the full power of a closure.

The trouble with closures having their own generic parameter lists is that closures are values. So imagine you could write:

let fn = { <T> (x: T) -> T in x }

What is the type of ‘fn’?

You should be able to do,

fn(3)
fn(“hello”)

So fn has a polymorphic type. Now when a polymorphic closure is appears immediately on the right hand side of a ‘let’ binding, it is equivalent to a local function today. But you can also do stuff like

let fn1 = { <T> (x: T, y: T) -> T in x }
let fn2 = { <T> (x: T, y: T) -> T in y }
let myFn = foo ? fn1 : fn2

Now ‘myFn’ is a value with a polymorphic type that is not immediately known to be a closure literal. Actually implementing this properly would require a substantial rework of all layers of the compiler and runtime.

I’m not saying any of this is impossible. It could certainly all be implemented with enough effort. However, there are two general concerns I have with all of this:

1) Does implementing this functionality justify the cost, both in terms of potential regressions, or just the opportunity cost in the team not spending the time working on something else?

2) How is it actually an improvement over local functions? If we make local functions look like closures, what have we actually gained? Local functions have the advantage that they look like functions — so a generic local function reads naturally to someone who’s used Swift before but has never seen a local function. On the other hand, a generic closure expression, were such a thing to exist, would be a new syntax that presumably would only appear in source rarely, confusing someone when they first encounter it.

Perhaps what we really need is better control over captures from local functions. Adding capture lists to local functions is something I would not be opposed to for instance, and should be possible to implement without much pain.

Slava

···

On Oct 27, 2017, at 9:42 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

The reason for showing code above is to demonstrate that the compiler could do this (in fact it could do better) - I am not suggesting anyone uses the above!

  -- Howard.

On 28 October 2017 at 10:01, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
That sounds like a bug, and it could occur with closure expressions also, since at the SILGen level and below they’re basically the same thing. Please file a bug if you come up with a reduced test case.

Slava

> On Oct 27, 2017, at 4:00 PM, Jon Gilbert <swiftevolution@jongilbert.com <mailto:swiftevolution@jongilbert.com>> wrote:
>
> I have run into nondescript compiler crashes (like segmentation faults, etc.) when using local functions to do certain things, like wrapping the parent function’s escaping closure arguments in other closures that capture variables from the parent function’s local scope, especially when those variables themselves are function types that do the wrapping, and therefore take closures as arguments. (Of course, all of these closures taking generic arguments conforming to protocols with associated types made things extra interesting.)
>
> I don’t know if this makes local functions actively harmful, or if it means function types in capture lists need to support @escaping, but it does remind me to go back and try to reproduce those weird issues in a sample project or playground page so I can make a bug report to Apple.
>
> Jonathan
>
>> On Oct 27, 2017, at 12:29, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>
>> I mean, we could remove a lot of language features without giving up turing completeness. But I’ve personally used local functions in multiple languages and found them useful. I certainly don’t see why the feature is actively harmful, which is the criteria for introducing a source breaking change in Swift 5.
>

_______________________________________________
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

+1

···

On Oct 28, 2017, at 1:47 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

How about
func localFunc[weak self](input: Double) {
}
?
Seems to be the obvious choice for me...

--
C. Keith Ray

* https://leanpub.com/wepntk <- buy my book?
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf
* http://agilesolutionspace.blogspot.com/

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

How about
func localFunc[weak self](input: Double) {
}
?
Seems to be the obvious choice for me...