[swift-evolution] Streamlining closures
Etan Kissling
kissling at oberon.ch
Mon Dec 14 06:33:34 CST 2015
Problem is that without the `in` you could conflict with custom defined operators.
Right now, you can go with
[1, 2, 3, 4, 5].map {
$0 + 1
}
Now assume a world where a user defined a custom postfix . or | operator.
var error = ""
someAsyncFunction() { error .
// body
}
Is the "error" a named variable inside the closure, or the one used in the outer scope?
Is the . our custom postfix operator, or is it the delimiter between the argument list and the body?
With `in`, this problem cannot occur, as `in` cannot be used as an identifier.
Also don't forget about the whole mess when it starts with capture lists and explicit return type.
someAsyncFunction() { [capture list] (arg1: Arg1Type, arg2: Arg2Type, arg3: Arg3Type) -> ReturnType in
// ...
}
Etan
> On 13 Dec 2015, at 15:36, Taras Zakharko via swift-evolution <swift-evolution at swift.org> wrote:
>
>> On 13 Dec 2015, at 15:26, Marc Knaup <marc at knaup.koeln <mailto:marc at knaup.koeln>> wrote:
>>
>> How would you write this with the new syntax?
>
>
>> On 13 Dec 2015, at 09:42, Thorsten Seitz <tseitz42 at icloud.com <mailto:tseitz42 at icloud.com>> wrote:
>>
>> I don't think the syntax { x in ... } is a hindrance for your proposal of turning all code blocks into closures, as all code blocks currently have no arguments and in that case both variants look the same. All code blocks with the exception of function bodies but I think having these looking different is ok.
>
>
> Point well taken :)
>
> Other suggestions (out of top of my head) would be
>
> // lambda-calculus inspired, but potentially confusing
> someAsyncFunction() { error .
> // …
> }
>
> // like above, kind of constructor notation, my personal favourite so far
> someAsyncFunction() { error |
> // …
> }
>
> // lest idiosyncratic but also verbose and ugly
> someAsyncFunction() func(error) {
> // …
> }
>
> But you are right of course that the syntax I proposed in the original mail does not work with these examples. Again, its more of a cosmetic thing anyway. I think the other point (streamlining the concept of closures in the language) is more important.
>
>> On 13 Dec 2015, at 09:42, Thorsten Seitz <tseitz42 at icloud.com <mailto:tseitz42 at icloud.com>> wrote:
>
>> Actually I was very surprised to see that your example with "local" returning a tuple did not work. The type inferencer should be able to infer the result type T to be a tuple without problems.
>
> Yep, I was also quite surprised. But it does not work for me as of Xcode 7C68
>
> Cheers,
>
> Taras
>
> P.S. Thorsten, sorry for wrong reply, I am not using mailing lists often and the behaviour of the mail client is a bit confusing.
>
>
>> On 13 Dec 2015, at 15:26, Marc Knaup <marc at knaup.koeln <mailto:marc at knaup.koeln>> wrote:
>>
>> How would you write this with the new syntax?
>>
>> someAsyncFunction() { error in
>> // …
>> }
>>
>> This doesn't make sense (like Thorsten explained earlier):
>>
>> someAsyncFunction() error {
>> // …
>> }
>>
>> I agree though that "in" is confusing and I never understood what "in" means here.
>> Since "do" is no longer used for loops but code blocks it would make sense to replace "in" with "do".
>>
>> someAsyncFunction() { error do
>> // …
>> }
>>
>>
>> On Sun, Dec 13, 2015 at 9:42 AM, Thorsten Seitz via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> Hi Taras,
>>
>> I don't think the syntax { x in ... } is a hindrance for your proposal of turning all code blocks into closures, as all code blocks currently have no arguments and in that case both variants look the same. All code blocks with the exception of function bodies but I think having these looking different is ok.
>>
>> An advantage of the current syntax is that you can leave off the parentheses when calling a closure if it is the last argument which already enables the syntax you would like for "local":
>>
>> let x = local {
>> ...
>> }
>>
>> This requires having the arguments inside the braces to allow for things like these:
>>
>> someCollection.forEach { x in ... }
>> File.open("some/path") { file in ... } // having the file closed automatically
>>
>> Such usage patterns would look awkward with arguments outside of the braces and they are very common and very useful.
>>
>> Actually I was very surprised to see that your example with "local" returning a tuple did not work. The type inferencer should be able to infer the result type T to be a tuple without problems.
>>
>> -Thorsten
>>
>> Am 13.12.2015 um 05:14 schrieb Taras Zakharko via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>:
>>
>>> Dear all,
>>>
>>> first of all, thank you for giving the community the opportunity to influence the future of Swift. This is a strong move, which shows how passionate the Swift team is about their language and how you guys want to make it the best.
>>>
>>> Personally, I am a huge fan of streamlined, logically concise concepts as well as syntax in programming languages. This is an area where Swift both excels at (love what you do with types in the compiler) but also sometimes lacking, as there seems to be a lot of idiosyncrasy, as evident from the huge number of language keywords and compiler attributes. Here, I would like to discuss some of the idiosyncrasy with closures. As I am trying to throw some random ideas out there, my post might be a bit chaotic, so please bear with me. Besides, I literally started playing around with Swift only few hours ago (I know, I know, I’m late), so my practical knowledge of the language is very limited.
>>>
>>> 1. I always wondered about the specific closure syntax { A -> B in … }. The type annotation is within the block for some reason and the there is the keyword ‘in’, which is usually associated with the for loop. My proposal would be to change this to simply:
>>>
>>> A -> B { … }
>>>
>>> That is, the closure declaration closely mirrors the function declaration, but without the 'func name’ part. This is far from being just a cosmetic change though. Rather, it has some far-reaching consequences to how the concept of closure is applied to the language, but I believe that this would make Swift much more streamlined and concise (and also solve some other issues in the process).
>>>
>>> The crucial point is that the type annotation is (and should be) optional. Which means that every code block { … } is a closure. I think this fundamentally makes a lot of sense. So, a function declaration is just a closure associated with a name identifier (aka. injected into the namespace). Similarly, code blocks used in statements such as for and do have local lexical scope, which makes them essentially compatible closures as far as I can see.
>>>
>>> Essentially, this approach radically simplifies the language both from the syntax standpoint — closures and funcs are using the same syntax, the notion of code blocks is removed from the language; but also from the semantics standpoint. For instance, one can give more formal definitions for statements, which opens the path of treating statements as expressions (see https://lists.swift.org/pipermail/swift-evolution/2015-December/000133.html <https://lists.swift.org/pipermail/swift-evolution/2015-December/000133.html>). For instance, current if is of type (Bool, ()->())->(), but it can easily be redeclared as (Bool, ()->T)->T, which opens way to nice things like
>>>
>>> let x = if(…) { .. some complex setup code..; value1} else { … other code..; value2}
>>>
>>> Similarly, do can be used to incapsulate auxiliary computations in the local scope (see below).
>>>
>>> 2. Another closure limitation is that return type inference does not work with longer closures (but they should). Here a particular use case. Due to my work, I program a lot with R (which is sometimes a neat language in its own right). A particular paradigm I find very useful is to use anonymous code blocks in local scope to prevent namespace pollution, e.g. (in R code)
>>>
>>> dataset <- local({
>>> a <- some_computation
>>> b <- some_computation
>>>
>>> a combined with b
>>> })
>>>
>>> // a and b are not visible in the outer scope anymore
>>>
>>> In JavaScript, people often use anonymous functions for the same effect. The nice thing about this paradigm is that it allows one to properly isolate data dependency from each other, give the compiler a better sense of variable lifespan and overall make your code much nicer to read. Of course, in Swift this can be done with nested functions or closures, but its the question of aesthetics. At any rate, the R function is very easy to emulate in Swift:
>>>
>>> func local<T>(closure: () -> T) -> T {
>>> return closure()
>>> }
>>>
>>> which works beautifully, e.g.
>>>
>>> let (a1,a2) = local({return(1, 2)}
>>>
>>> However, if I want to use more complex code, I need to explicitly declare the closure type:
>>>
>>> let (a1, a2) = local({ () -> (Int, Int) in
>>> let x2 = x*2
>>>
>>> return (x2, x2+1)
>>> })
>>>
>>>
>>> This is bulky, and frankly, unnecessary, as the compiler can easily infer the return type of the closure.
>>>
>>> BTW, combined with proposal in 1., this style of programming just becomes the beautiful
>>>
>>> let (a1, a2) = do {
>>> .. do something ..
>>> return(…, …) // or simply (…, …)
>>> }
>>>
>>>
>>> Summary TL;DR version of the proposal:
>>>
>>> replace the {A->B in …} closure syntax by A->B {…} — where type declaration is optional, remove the concept of code block (replace it by closure) and statements that take blocks/closures typed expressions. Improve type inference for return values. Current syntactic sugar for closures such as anonymous arguments and last line as return values should be preserved.
>>>
>>> Advantages: streamlines the language, solves a number of issues at the same time (idiosyncrasy, operators as expressions). In the end, makes the language easier to use and also to understand conceptually.
>>>
>>> Disadvantages: I honestly can’t see a single one
>>>
>>> Implementation challenge: would requires a parser change that might be non-trivial. I can also imagine that a lot of refactoring in the compiler code is necessary in regards to how statements work — but this might pay off as in the end, the compiler only needs to deal with closures and calls, which simplifies optimisation. And Swift is already great at aggressive inlining of closures.
>>>
>>> Impact on existing code: the syntax of closure declarations can be automatically ported as the rewrite rule is trivial. As far as I can see, changing code blocks to closures won’t have any impact on the semantics of the control structures, because the blocks are already using lexical scope.
>>>
>>> Eager to hear your thoughts on this!
>>>
>>> Cheers,
>>>
>>> Taras <open.gif>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> <open.gif>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>
>>
>
>> On 13 Dec 2015, at 15:26, Marc Knaup <marc at knaup.koeln <mailto:marc at knaup.koeln>> wrote:
>>
>> How would you write this with the new syntax?
>>
>> someAsyncFunction() { error in
>> // …
>> }
>>
>> This doesn't make sense (like Thorsten explained earlier):
>>
>> someAsyncFunction() error {
>> // …
>> }
>>
>> I agree though that "in" is confusing and I never understood what "in" means here.
>> Since "do" is no longer used for loops but code blocks it would make sense to replace "in" with "do".
>>
>> someAsyncFunction() { error do
>> // …
>> }
>>
>>
>> On Sun, Dec 13, 2015 at 9:42 AM, Thorsten Seitz via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> Hi Taras,
>>
>> I don't think the syntax { x in ... } is a hindrance for your proposal of turning all code blocks into closures, as all code blocks currently have no arguments and in that case both variants look the same. All code blocks with the exception of function bodies but I think having these looking different is ok.
>>
>> An advantage of the current syntax is that you can leave off the parentheses when calling a closure if it is the last argument which already enables the syntax you would like for "local":
>>
>> let x = local {
>> ...
>> }
>>
>> This requires having the arguments inside the braces to allow for things like these:
>>
>> someCollection.forEach { x in ... }
>> File.open("some/path") { file in ... } // having the file closed automatically
>>
>> Such usage patterns would look awkward with arguments outside of the braces and they are very common and very useful.
>>
>> Actually I was very surprised to see that your example with "local" returning a tuple did not work. The type inferencer should be able to infer the result type T to be a tuple without problems.
>>
>> -Thorsten
>>
>> Am 13.12.2015 um 05:14 schrieb Taras Zakharko via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>:
>>
>>> Dear all,
>>>
>>> first of all, thank you for giving the community the opportunity to influence the future of Swift. This is a strong move, which shows how passionate the Swift team is about their language and how you guys want to make it the best.
>>>
>>> Personally, I am a huge fan of streamlined, logically concise concepts as well as syntax in programming languages. This is an area where Swift both excels at (love what you do with types in the compiler) but also sometimes lacking, as there seems to be a lot of idiosyncrasy, as evident from the huge number of language keywords and compiler attributes. Here, I would like to discuss some of the idiosyncrasy with closures. As I am trying to throw some random ideas out there, my post might be a bit chaotic, so please bear with me. Besides, I literally started playing around with Swift only few hours ago (I know, I know, I’m late), so my practical knowledge of the language is very limited.
>>>
>>> 1. I always wondered about the specific closure syntax { A -> B in … }. The type annotation is within the block for some reason and the there is the keyword ‘in’, which is usually associated with the for loop. My proposal would be to change this to simply:
>>>
>>> A -> B { … }
>>>
>>> That is, the closure declaration closely mirrors the function declaration, but without the 'func name’ part. This is far from being just a cosmetic change though. Rather, it has some far-reaching consequences to how the concept of closure is applied to the language, but I believe that this would make Swift much more streamlined and concise (and also solve some other issues in the process).
>>>
>>> The crucial point is that the type annotation is (and should be) optional. Which means that every code block { … } is a closure. I think this fundamentally makes a lot of sense. So, a function declaration is just a closure associated with a name identifier (aka. injected into the namespace). Similarly, code blocks used in statements such as for and do have local lexical scope, which makes them essentially compatible closures as far as I can see.
>>>
>>> Essentially, this approach radically simplifies the language both from the syntax standpoint — closures and funcs are using the same syntax, the notion of code blocks is removed from the language; but also from the semantics standpoint. For instance, one can give more formal definitions for statements, which opens the path of treating statements as expressions (see https://lists.swift.org/pipermail/swift-evolution/2015-December/000133.html <https://lists.swift.org/pipermail/swift-evolution/2015-December/000133.html>). For instance, current if is of type (Bool, ()->())->(), but it can easily be redeclared as (Bool, ()->T)->T, which opens way to nice things like
>>>
>>> let x = if(…) { .. some complex setup code..; value1} else { … other code..; value2}
>>>
>>> Similarly, do can be used to incapsulate auxiliary computations in the local scope (see below).
>>>
>>> 2. Another closure limitation is that return type inference does not work with longer closures (but they should). Here a particular use case. Due to my work, I program a lot with R (which is sometimes a neat language in its own right). A particular paradigm I find very useful is to use anonymous code blocks in local scope to prevent namespace pollution, e.g. (in R code)
>>>
>>> dataset <- local({
>>> a <- some_computation
>>> b <- some_computation
>>>
>>> a combined with b
>>> })
>>>
>>> // a and b are not visible in the outer scope anymore
>>>
>>> In JavaScript, people often use anonymous functions for the same effect. The nice thing about this paradigm is that it allows one to properly isolate data dependency from each other, give the compiler a better sense of variable lifespan and overall make your code much nicer to read. Of course, in Swift this can be done with nested functions or closures, but its the question of aesthetics. At any rate, the R function is very easy to emulate in Swift:
>>>
>>> func local<T>(closure: () -> T) -> T {
>>> return closure()
>>> }
>>>
>>> which works beautifully, e.g.
>>>
>>> let (a1,a2) = local({return(1, 2)}
>>>
>>> However, if I want to use more complex code, I need to explicitly declare the closure type:
>>>
>>> let (a1, a2) = local({ () -> (Int, Int) in
>>> let x2 = x*2
>>>
>>> return (x2, x2+1)
>>> })
>>>
>>>
>>> This is bulky, and frankly, unnecessary, as the compiler can easily infer the return type of the closure.
>>>
>>> BTW, combined with proposal in 1., this style of programming just becomes the beautiful
>>>
>>> let (a1, a2) = do {
>>> .. do something ..
>>> return(…, …) // or simply (…, …)
>>> }
>>>
>>>
>>> Summary TL;DR version of the proposal:
>>>
>>> replace the {A->B in …} closure syntax by A->B {…} — where type declaration is optional, remove the concept of code block (replace it by closure) and statements that take blocks/closures typed expressions. Improve type inference for return values. Current syntactic sugar for closures such as anonymous arguments and last line as return values should be preserved.
>>>
>>> Advantages: streamlines the language, solves a number of issues at the same time (idiosyncrasy, operators as expressions). In the end, makes the language easier to use and also to understand conceptually.
>>>
>>> Disadvantages: I honestly can’t see a single one
>>>
>>> Implementation challenge: would requires a parser change that might be non-trivial. I can also imagine that a lot of refactoring in the compiler code is necessary in regards to how statements work — but this might pay off as in the end, the compiler only needs to deal with closures and calls, which simplifies optimisation. And Swift is already great at aggressive inlining of closures.
>>>
>>> Impact on existing code: the syntax of closure declarations can be automatically ported as the rewrite rule is trivial. As far as I can see, changing code blocks to closures won’t have any impact on the semantics of the control structures, because the blocks are already using lexical scope.
>>>
>>> Eager to hear your thoughts on this!
>>>
>>> Cheers,
>>>
>>> Taras
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>
>>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151214/12a3a860/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: Message signed with OpenPGP using GPGMail
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151214/12a3a860/attachment-0001.sig>
More information about the swift-evolution
mailing list