Closure Syntax


(Ethan Diamond) #1

I realize this is on the commonly rejected list, but I really find closure
syntax to be one of the more unintuitive and unreadable parts of Swift so
I'd like to try to influence it. "Clarity at the point of use" is one of
the main stated goals of Swift, and the fact that
http://goshdarnclosuresyntax.com/ exists shows me that it's not just me
that thinks the syntax could be improved. I believe that we can get very
close to the same results in line length and expressiveness while making
the language much more readable and clear.

Let's start with a few use cases to illustrate what I mean when I say that
reading closure syntax is difficult. Most programmers scan left to right,
so when I see this property declaration:

var thing: Int

My brain reads that as a property of a class that's an integer. Unless of
course there's this:

var thing: Int -> Int

Boom, context switch. If you've read "Int" than any following syntax should
be a modifier on Int. For example, Int? works great, because in my head
it's still an Integer, just an optional form of that Integer. While it's
not a huge change in that example, lets take a more complicated one:

var thing: (String -> (), Int, (Int, Int) -> Bool)) -> Bool

Reading that left to right requires all sorts of context switching in my
brain. I can't even tell at first glance how many params are in that
closure. Reading left to right, you read "First parameter, string, no wait,
closure that takes string, and returns void. Second param, Int. Third
param, tuple with two ints, no wait, closure that takes two ints and
returns bool." I just doesn't have much clarity.

I believe it's already been proposed, but I don't feel there's a strong
enough difference between a closure and a function to justify a different
syntax. Let's replace my examples above with anonymous function syntax.

var thing: func (Int) -> Int

Reading left to right, it reads the way that I think about it "A function
that takes an integer and returns an integer."

var thing: func(func (String), Int, func (Int, Int) -> Bool) -> Bool

Again, reading left to right this is a win. "Thing is an anonymous
function. First param, a function that takes a string. Second param, Int.
Third param, a function that takes two ints and returns bool." It reads
like people think.

Another strength is it lets us both use the same syntax for closures as we
would expect, while letting us use just about all of the same shorthands we
gain with closures. We could call normally like this, with the return type
implied when async is called:

func async(callback: func (Bool) -> Bool))

async(func (successful: Bool) {
   return !successful
});

We could still permit this:

func async(callback: func ())

async {
  //Do callback stuff here
}

We could still permit this:

func sort(sortfunc: func(Int, Int) -> Bool)

sort {
  $0 > $1
}

We could add this:

let greaterThan: func (number: Int) -> Bool = {
   number > 5
}

There would also be a few small wins, such as no longer needing "()" to
represent a void return, since any function without a return type would
imply a void return.

I understand that a big part of the decision for current closure syntax is
to help the compiler, but I believe by doing so you're going against the
main principles you laid out in your api design guidelines (
https://swift.org/documentation/api-design-guidelines.html). Current
closure syntax is not clear and breaks precedent of all other function like
declarations having the parameters listed outside of the curly braces.

Thanks for listening, and great job on Swift so far.


(Chris Lattner) #2

I realize this is on the commonly rejected list, but I really find closure syntax to be one of the more unintuitive and unreadable parts of Swift so I'd like to try to influence it. "Clarity at the point of use" is one of the main stated goals of Swift, and the fact that http://goshdarnclosuresyntax.com/ exists shows me that it's not just me that thinks the syntax could be improved. I believe that we can get very close to the same results in line length and expressiveness while making the language much more readable and clear.

FWIW, I think that web site exists as a continuation of the “blocks” web site.

From your description, it sounds like you might want to try out nested functions. They have a more explicit syntax, and still provide the same “closure” power as closure expressions.

-Chris

···

On Dec 27, 2015, at 2:30 PM, Ethan Diamond via swift-evolution <swift-evolution@swift.org> wrote:

Let's start with a few use cases to illustrate what I mean when I say that reading closure syntax is difficult. Most programmers scan left to right, so when I see this property declaration:

var thing: Int

My brain reads that as a property of a class that's an integer. Unless of course there's this:

var thing: Int -> Int

Boom, context switch. If you've read "Int" than any following syntax should be a modifier on Int. For example, Int? works great, because in my head it's still an Integer, just an optional form of that Integer. While it's not a huge change in that example, lets take a more complicated one:

var thing: (String -> (), Int, (Int, Int) -> Bool)) -> Bool

Reading that left to right requires all sorts of context switching in my brain. I can't even tell at first glance how many params are in that closure. Reading left to right, you read "First parameter, string, no wait, closure that takes string, and returns void. Second param, Int. Third param, tuple with two ints, no wait, closure that takes two ints and returns bool." I just doesn't have much clarity.

I believe it's already been proposed, but I don't feel there's a strong enough difference between a closure and a function to justify a different syntax. Let's replace my examples above with anonymous function syntax.

var thing: func (Int) -> Int

Reading left to right, it reads the way that I think about it "A function that takes an integer and returns an integer."

var thing: func(func (String), Int, func (Int, Int) -> Bool) -> Bool

Again, reading left to right this is a win. "Thing is an anonymous function. First param, a function that takes a string. Second param, Int. Third param, a function that takes two ints and returns bool." It reads like people think.

Another strength is it lets us both use the same syntax for closures as we would expect, while letting us use just about all of the same shorthands we gain with closures. We could call normally like this, with the return type implied when async is called:

func async(callback: func (Bool) -> Bool))

async(func (successful: Bool) {
   return !successful
});

We could still permit this:

func async(callback: func ())

async {
  //Do callback stuff here
}

We could still permit this:

func sort(sortfunc: func(Int, Int) -> Bool)

sort {
  $0 > $1
}

We could add this:

let greaterThan: func (number: Int) -> Bool = {
   number > 5
}

There would also be a few small wins, such as no longer needing "()" to represent a void return, since any function without a return type would imply a void return.

I understand that a big part of the decision for current closure syntax is to help the compiler, but I believe by doing so you're going against the main principles you laid out in your api design guidelines (https://swift.org/documentation/api-design-guidelines.html). Current closure syntax is not clear and breaks precedent of all other function like declarations having the parameters listed outside of the curly braces.

Thanks for listening, and great job on Swift so far.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Ethan Diamond) #3

I guess the core of the question for me is why have the concept of a
closure at all when they're harder to read while acting the same way that
functions do? I'm not a huge fan of Javascript, but the consistancy between
function declarations and anonymous functions is something I feel they got
right. JS syntax for everything that receives parameters and possibly
returns a value is entirely consistent.

Let's take the anonymous function style:

func asyncWithCallback(_ : func (String) -> Bool)

asyncWithCallback(func (param) {
    return param == "string"
})

Particularly for someone new to the language is both clearer and shorter
than this, which makes the caller rewrite the return type in the closure:

func asyncWithCallback(_ : String -> Bool)

asyncWithCallback {
  (param: String) -> Bool in
  return param == "string"
}

It still fits your unwritten rule to be able to compose language features
in Swift, assuming you leave the same syntactic sugar:

func if (_ control: Bool, _ path: func ()) {

  if (control) {

     path()

  }

}

if (a == b) {

  //LETS DO SOME STUFF

}

I know it's a big change to the language, but I feel like it's better
in just about every case I can think of without losing the Swiftiness
we all enjoy. It certainly makes this easier to read. Even in one of
the examples in the Swift guide
(https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html)
you're essentially using the syntax i'm proposing on the incrementer:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {

    var runningTotal = 0

    func incrementer() -> Int {

        runningTotal += amount

        return runningTotal

    }

    return incrementer

}

I can't really fix the unclear chained return in the declaration, but
doesn't the return of the anonymous function look similar to the
above, just as you would expect?

func makeIncrementer(forIncrement amount: Int) -> func() -> Int {

   var runningTotal = 0

   return func () -> Int {

    runningTotal += amount

    return runningTotal

  }

}

But if I wanted to use closure syntax, It'd be different from the
example above in the Swift handbook for no real good reason:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {

    var runningTotal = 0

    return {

        () -> Int in

        runningTotal += amount

        return runningTotal

    }

}

In the commonly rejected list, Jordan wrote:

"We thought a lot about this, and settled on the current syntax
(inside the braces) for several reasons, the main one being that it's
much easier to parse. Without this, the compiler would have to stop
whatever it's currently doing when it sees '->'."

I don't believe that's a problem when using the proposed func syntax,
since by definition "->" will only follow a func (param) statement in
all cases. The compiler can make more assumptions about what it's
parsing. I think it might limit what you have to work with to the
compilers benefit.

Thanks,

- Ethan

···

On Mon, Dec 28, 2015 at 5:44 PM, Chris Lattner <clattner@apple.com> wrote:

On Dec 27, 2015, at 2:30 PM, Ethan Diamond via swift-evolution < > swift-evolution@swift.org> wrote:

I realize this is on the commonly rejected list, but I really find closure
syntax to be one of the more unintuitive and unreadable parts of Swift so
I'd like to try to influence it. "Clarity at the point of use" is one of
the main stated goals of Swift, and the fact that
http://goshdarnclosuresyntax.com/ exists shows me that it's not just me
that thinks the syntax could be improved. I believe that we can get very
close to the same results in line length and expressiveness while making
the language much more readable and clear.

FWIW, I think that web site exists as a continuation of the “blocks” web
site.

From your description, it sounds like you might want to try out nested
functions. They have a more explicit syntax, and still provide the same
“closure” power as closure expressions.

-Chris

Let's start with a few use cases to illustrate what I mean when I say that
reading closure syntax is difficult. Most programmers scan left to right,
so when I see this property declaration:

var thing: Int

My brain reads that as a property of a class that's an integer. Unless of
course there's this:

var thing: Int -> Int

Boom, context switch. If you've read "Int" than any following syntax
should be a modifier on Int. For example, Int? works great, because in my
head it's still an Integer, just an optional form of that Integer. While
it's not a huge change in that example, lets take a more complicated one:

var thing: (String -> (), Int, (Int, Int) -> Bool)) -> Bool

Reading that left to right requires all sorts of context switching in my
brain. I can't even tell at first glance how many params are in that
closure. Reading left to right, you read "First parameter, string, no wait,
closure that takes string, and returns void. Second param, Int. Third
param, tuple with two ints, no wait, closure that takes two ints and
returns bool." I just doesn't have much clarity.

I believe it's already been proposed, but I don't feel there's a strong
enough difference between a closure and a function to justify a different
syntax. Let's replace my examples above with anonymous function syntax.

var thing: func (Int) -> Int

Reading left to right, it reads the way that I think about it "A function
that takes an integer and returns an integer."

var thing: func(func (String), Int, func (Int, Int) -> Bool) -> Bool

Again, reading left to right this is a win. "Thing is an anonymous
function. First param, a function that takes a string. Second param, Int.
Third param, a function that takes two ints and returns bool." It reads
like people think.

Another strength is it lets us both use the same syntax for closures as we
would expect, while letting us use just about all of the same shorthands we
gain with closures. We could call normally like this, with the return type
implied when async is called:

func async(callback: func (Bool) -> Bool))

async(func (successful: Bool) {
   return !successful
});

We could still permit this:

func async(callback: func ())

async {
  //Do callback stuff here
}

We could still permit this:

func sort(sortfunc: func(Int, Int) -> Bool)

sort {
  $0 > $1
}

We could add this:

let greaterThan: func (number: Int) -> Bool = {
   number > 5
}

There would also be a few small wins, such as no longer needing "()" to
represent a void return, since any function without a return type would
imply a void return.

I understand that a big part of the decision for current closure syntax is
to help the compiler, but I believe by doing so you're going against the
main principles you laid out in your api design guidelines (
https://swift.org/documentation/api-design-guidelines.html). Current
closure syntax is not clear and breaks precedent of all other function like
declarations having the parameters listed outside of the curly braces.

Thanks for listening, and great job on Swift so far.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Waite) #4

I guess the core of the question for me is why have the concept of a closure at all when they're harder to read while acting the same way that functions do? I'm not a huge fan of Javascript, but the consistancy between function declarations and anonymous functions is something I feel they got right. JS syntax for everything that receives parameters and possibly returns a value is entirely consistent.

Some of this is because Javascript doesn't have to declare types or return values and it is even loose with arguments and is somewhat confusing (and dynamic) with “this”. For Swift, which requires knowledge about the arguments and return values needed by callers, it makes sense to have two different syntaxes depending on whether or not this knowledge is being defined by contract or being inferred by context.

And with ES2015 there are now multiple styles, partially to resolve the issues with capturing “this” and partially because writing “function” everywhere is visually distracting.

Let's take the anonymous function style:

func asyncWithCallback(_ : func (String) -> Bool)

asyncWithCallback(func (param) {
    return param == "string"
})

Particularly for someone new to the language is both clearer and shorter than this, which makes the caller rewrite the return type in the closure:

func asyncWithCallback(_ : String -> Bool)

asyncWithCallback {
  (param: String) -> Bool in
  return param == "string"
}

Or I could use:
asyncWithCallback {
    param in
    return param == "string"
}

or even:
asyncWithCallback { $0 == "string” }

A function defines a name and a contract for use and definition, while closures are only semantically valid once defined by a context. I can understand the stylistic desire to have closures declare arguments outside a block and to be closer to function syntax. However, using “func" anonymously to indicate a different syntax with a different set of options, potentially inferred input types, and an inferred existence of output as well as output type might be equally confusing. Perhaps another keyword could be used for this purpose.

However this new keyword would need to work with, and avoid adding visual distraction to, the “$0 == “string” case above.

It still fits your unwritten rule to be able to compose language features in Swift, assuming you leave the same syntactic sugar:
func if (_ control: Bool, _ path: func ()) {
  if (control) {
     path()
  }
}
if (a == b) {
  //LETS DO SOME STUFF
}

You probably realize this but closures and blocks have different control flow, so for instance the above would absorb/malfunction on returns as well as attempts to break/continue outer loops.

-DW

···

On Dec 29, 2015, at 5:18 PM, Ethan Diamond via swift-evolution <swift-evolution@swift.org> wrote:


(Ethan Diamond) #5

Reasonable. That all makes sense.

For clarity's sake, there's really two syntactic issues that I think would
be good to address. The first, which I think is the far bigger problem,
which is that right now a list of params with closures is very hard to
read. For example, this requires mental effort to decode whether this is
returning an Int, or a closure returning an Int:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {

}

And the second being that stylistically, having the params on the
inside of the closure is breaking precedent of params outside of the
block of code without gaining much.

Perhaps we could adopt the Obj-C ^ as a closure indicator. I think
with existing Swift syntax, a lot of the confusion that caused
www.fuckingblocksyntax.com would be avoided.

=== Local variable / property ===:

var closure = ^(Int) -> Bool

or

var closure = ^(number: Int) -> Bool {

    if (number == 3) { return true }

}

=== Method param ===

func makeIncrementer(forIncrement amount: Int) -> ^() -> Int {

or

func makeIncrementer(forIncrement amount: Int) -> ^(() -> Int) {

=== Argument ===

func makeIncrementer(forIncrement amount: ^((Int) -> Bool)) -> Bool {}

makeIncrementer(forIncrement: ^(number: Int) {

   if (number == 3) { return true }

})

=== typealias ===

typealias CustomType = ^(String) -> Bool

=== Takes no params, returns void (We take for granted if there's no
return, it's void) ===

^()

And I think we can use all the shorthands we currently use:

=== Calling no params, takes void ===

functionWithVoidCallback {

   //Do a thing

}

=== Using $0 $1 syntax ===

func compare(_ comparator: ^(a: Int, b: Int) -> Bool) { }

compare {

  %0 > %1

}

By having ^ mark an upcoming closure, I think it's a lot easier to
follow what's going on with declarations because as you read left to
right, you're prepped that a closure syntax is coming up as you read
left to right. It also allows you to keep the params outside of the
closure, which I think is a win. Closure params would also have the
same syntax everywhere, and be extremely similar to normal method
calls which would be easier for new Swift users.

-E

···

On Tue, Dec 29, 2015 at 10:13 PM, David Waite <david@alkaline-solutions.com> wrote:

On Dec 29, 2015, at 5:18 PM, Ethan Diamond via swift-evolution < > swift-evolution@swift.org> wrote:

I guess the core of the question for me is why have the concept of a
closure at all when they're harder to read while acting the same way that
functions do? I'm not a huge fan of Javascript, but the consistancy between
function declarations and anonymous functions is something I feel they got
right. JS syntax for everything that receives parameters and possibly
returns a value is entirely consistent.

Some of this is because Javascript doesn't have to declare types or return
values and it is even loose with arguments and is somewhat confusing (and
dynamic) with “this”. For Swift, which requires knowledge about the
arguments and return values needed by callers, it makes sense to have two
different syntaxes depending on whether or not this knowledge is being
defined by contract or being inferred by context.

And with ES2015 there are now multiple styles, partially to resolve the
issues with capturing “this” and partially because writing “function”
everywhere is visually distracting.

Let's take the anonymous function style:

func asyncWithCallback(_ : func (String) -> Bool)

asyncWithCallback(func (param) {
    return param == "string"
})

Particularly for someone new to the language is both clearer and shorter
than this, which makes the caller rewrite the return type in the closure:

func asyncWithCallback(_ : String -> Bool)

asyncWithCallback {
  (param: String) -> Bool in
  return param == "string"
}

Or I could use:

asyncWithCallback {
    param in
    return param == "string"
}

or even:

asyncWithCallback { $0 == "string” }

A function defines a name and a contract for use and definition, while
closures are only semantically valid once defined by a context. I can
understand the stylistic desire to have closures declare arguments outside
a block and to be closer to function syntax. However, using “func"
anonymously to indicate a different syntax with a different set of options,
potentially inferred input types, and an inferred existence of output as
well as output type might be equally confusing. Perhaps another keyword
could be used for this purpose.

However this new keyword would need to work with, and avoid adding visual
distraction to, the “$0 == “string” case above.

It still fits your unwritten rule to be able to compose language features
in Swift, assuming you leave the same syntactic sugar:

func if (_ control: Bool, _ path: func ()) {

  if (control) {

     path()

  }

}

if (a == b) {

  //LETS DO SOME STUFF

}

You probably realize this but closures and blocks have different control
flow, so for instance the above would absorb/malfunction on returns as well
as attempts to break/continue outer loops.

-DW


(David Waite) #6

For clarity's sake, there's really two syntactic issues that I think would be good to address. The first, which I think is the far bigger problem, which is that right now a list of params with closures is very hard to read. For example, this requires mental effort to decode whether this is returning an Int, or a closure returning an Int:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
}

I look at currying like a right-associative operator (like the ternary operator, or assignments in languages like C). There is a function that takes no arguments and returns an int, that you get from a function that takes an int argument.

Changing this to say

func makeIncrementer(forIncrement amount: Int) -> func () -> Int {

feels like it is changing the behavior to that of a non-associative operator.

One interesting result would be if by removing the ternary operator as it stands today and changing how currying heppens, swift wound up having no right associative operators left in core.

And the second being that stylistically, having the params on the inside of the closure is breaking precedent of params outside of the block of code without gaining much.
Perhaps we could adopt the Obj-C ^ as a closure indicator. I think with existing Swift syntax, a lot of the confusion that caused www.fuckingblocksyntax.com <http://www.fuckingblocksyntax.com/> would be avoided.

<snip>

makeIncrementer(forIncrement: ^(number: Int) {
   if (number == 3) { return true }
})

Why does the block now have to be declared within the function parameters?
Why do I have to declare the type of number now?
Why do I *not* have to declare the return type as being a boolean?
Would this syntax still support single expression implicit returns, e.g. current Swift

[1,2,3].map() { number in number == 3 }

$R0: [Bool] = 3 values {
  [0] = false
  [1] = false
  [2] = true
}

By having ^ mark an upcoming closure, I think it's a lot easier to follow what's going on with declarations because as you read left to right, you're prepped that a closure syntax is coming up as you read left to right. It also allows you to keep the params outside of the closure, which I think is a win. Closure params would also have the same syntax everywhere, and be extremely similar to normal method calls which would be easier for new Swift users.

There are three pieces at play here IMHO:
1. How functions (global and on types) are declared and implemented
2. How function specifications are indicated as types
3. How anonymous functions are declared and implemented

When I declare a global or type-specific function, I can specify its name and arguments, assign external names to them (as well as internal names), and declare my return value. The types, even if generic, must be known for the function implementation to pass semantic checks.

The syntax for functions as types are really just a list of input and output types. There are not (for instance) named parameter requirements on the implementing functions. But the input and return types must be known to declare a new function type.

Anonymous functions assume a great deal of type information from the context in which they are used. This is why you can get away with not declaring input types or even input names (using $0, $1, etc), the return type or even if a value is returned. The code is mapped into the context it is needed, then semantic evaluation is done.

You are attempting to change #2 and #3 at the same time, when really they have quite different needs. I assume the reason anonymous functions (aka closures in Swift) have their argument names on the inside of the block is because the argument names have no external bearing whatsoever. Considering I may or may not declare type information or even give all the parameters names, this makes a certain kind of sense. If you were to move the argument names outside the block, you would need a syntax specifically for that. There are enough options that reusing the same base syntax between 1 and 3 or 2 and 3 would likely just _feel_ wrong.

There are enough rules about escape analysis to possibly make sense to have function blocks, do blocks, repeat blocks, switch blocks, for…in blocks, etc all be considered ‘kinds’ of blocks, and for closure blocks to be on that list. It possibly makes sense for them to have a keyword just like all the other blocks to simplify compiler and human visual parsing. But I don’t envy the effort of redesigning and redecorating that particular bike shed :slight_smile:

-DW


(Ethan Diamond) #7

*There are three pieces at play here IMHO:*
*1. How functions (global and on types) are declared and implemented*
*2. How function specifications are indicated as types*
*3. How anonymous functions are declared and implemented*

Agreed, so lets flush out the rules a little more and see if we can find a
common language for all three, since they're interconnected. For the sake
of clarity, I'm going to refer to closures as blocks, since they're
effectively the same thing. I think we should begin with these three rules:

1. Let's try to keep precedent for function calls, since blocks are pretty
much functions
2. A ^ signifies that we are now entering a block context with regards to
syntax
3. Just as in a lot of the rest of Swift, let's infer information we can,
but optionally allow it to be respecified if it makes the syntax clearer

From there, I think the solution to your #2 (*How function specifications

are indicated as types) *naturally presents itself. This is a normal
function declaration:

func funcName(param) -> return_type

A block is effectively a function without a name - an anonymous function.
So we remove the name. I also agree with your earlier points that func is
inappropriate terminology for what we're trying to do so we remove that
too. Using the ^ rule to signify a block, we get these options, which I
think should both be valid:

^(param) -> return_type
^((param) -> return_type)

Which I find to be easier to read than the current closure type:

(param) -> return_type

because it shares syntax with tuples up until the ->, the () are optional
making the param share syntax with whatever type the param is, and because
when nested closure type syntax become particularly nasty to read.

Indeed, I think it gives us the opportunity for one more gain here. We have
functions with named parameters because of Swift's goal of clarity. This
gives us the opportunity for blocks to also have named parameters. Which
one of these is better?

func fetchHTML(callback: (String, Int) -> Error?)

or

func fetchHTML(callback: ^(url: String, allowedResponseCode: Int) -> Error?)

The named params aren't enforced when the block is defined, but provides
hints for the Swift user who defines the block body. They can rename those
params if they want, but it gives them a hint on how those params will be
used, and will autocomplete when they go to write the body with those names.

From there, I think we have the answer to your question #1 as well (*How

functions are declared and implemented) *in that aside from using our new
block type syntax for their params, they are completely unchanged. However,
the new block type syntax makes function declarations easier to read.

As I listed before, we take this, which I find hard to read at a glance:

func makeIncrementer(forIncrement amount: Int) -> () -> Int { ... }

and replace it with something better:

func makeIncrementer(forIncrement amount: Int) -> ^() -> Int { ... }

or, what I feel should be an option with the ^ syntax:

func makeIncrementer(forIncrement amount: Int) -> ^(() -> Int) { ... }

I don't think you can look at that and tell me it's not clearer to read.

Your point 3 (*How anonymous functions are declared and implemented) *is a
bit trickier to explain, but I think the explanation arises naturally from
the three rules.

Let's start with rule 1, and look at the way we already define an call a
function:

func isNumberFour(number: Int) -> Bool {
    return number == 4
}
isNumberFour(4)

Because we already know the param type and the return type, we don't need
to respecify that information when we call the function. Using the same
logic we used to come up with the block type syntax - removing "func" and
since it has no name, basically using ^ as a signifier - we're going to be
able to reason out exactly what this would look like:

^(number: Int) -> Bool {
    return number == 4
}

Let's take the case where a function has a block as a parameter:

func isNumberFour(calculateNumber: ^((Int) -> Int)) -> Bool

How do I call this? If we're not using rule 3, inferring what we can, we
would write out this, which I think should be valid syntax if desired by
Swift's users:

let isFour: Bool = isNumberFour(calculateNumber: ^(number: Int) -> Int {
    return number * 2
})

But there's no reason to be that verbose. We have the isNumberFour
declaration. Much like we don't need to specify that isFour is a Bool,
Swift should also allow this:

let isFour = isNumberFour(calculateNumber: ^(number) {
   return number * 2
})

The compiler already knows enough about the block for that to work. We
named the Int param for the calculateNumber block, so autocomplete can
finish this for us. However, I also think it should be valid to be able to
change the name when the block's body is defined. So this would also be
valid:

let isFour = isNumberFour(calculateNumber: ^(amount) {
   return amount * 2
})

So the rules we follow are pretty simple. When the block's body is defined,
we only require users to specify the data the compiler can't infer, while
having the option to specify the rest. I believe this is exactly what we're
doing already with closures, we're just moving the part preceding the "in"
to the outside of the method and prefixing it with a ^. It makes closures
easier to reason about the syntax, and easier to read.

There are a few shorthands that would go with it. Let's take the var /
property case. Without using rule 3, removing syntax we can infer, we get
this:

var isNumberFour: ^(number: Int) -> Bool = ^(number: Int) -> Bool {
    return number == 4
}
isNumberFour(4)

But we already know the parameters, return type, and name of the
parameters. So following rule 3, we can reduce this to:

var isNumberFour: ^(Int) -> Bool = ^(number) {
    return number == 4
}

But I would propose we allow this shorthand, since it's nicer to read:

var isNumberFour: ^(number: Int) -> Bool = {
    return number == 4
}

As previously said, if there's no return type specified, we assume void.
Also, if there's no params and returns void, you can bypass the ^() all
together. So:

func async(_ callback: ^())

async(^() {
  //Do the callback
})

could still be written as:

async {
  //Do the callback
}

Much like closures in current Swift, if we want to use the $0 $1 shorthand,
we can also skip the block param piece:

func sort(_ comparator:^((a: Int, b: Int) -> Bool))

sort(^(a, b) {
  return a > b
})

could be written as:

sort {
  return $0 > $1
}

And to answer your question, "Would this syntax still support single
expression implicit returns, e.g. current Swift," the answer is still yes:

[1,2,3].map(^(number) {number == 3})

I feel like this syntax feels intuitive, gets rid of the weird "in" syntax,
and is closer to the function syntax people are used to. It also makes
reading block types easier, particularly when nested. I know there's a lot
of closure momentum to overcome at this point, but I really the time it
would take to change this would be worth it down the road.

-E

···

On Wed, Dec 30, 2015 at 6:48 PM, David Waite <david@alkaline-solutions.com> wrote:

For clarity's sake, there's really two syntactic issues that I think would
be good to address. The first, which I think is the far bigger problem,
which is that right now a list of params with closures is very hard to
read. For example, this requires mental effort to decode whether this is
returning an Int, or a closure returning an Int:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {

}

I look at currying like a right-associative operator (like the ternary
operator, or assignments in languages like C). There is a function that
takes no arguments and returns an int, that you get from a function that
takes an int argument.

Changing this to say

func makeIncrementer(forIncrement amount: Int) -> func () -> Int {

feels like it is changing the behavior to that of a non-associative
operator.

One interesting result would be if by removing the ternary operator as it
stands today and changing how currying heppens, swift wound up having no
right associative operators left in core.

And the second being that stylistically, having the params on the inside of the closure is breaking precedent of params outside of the block of code without gaining much.

Perhaps we could adopt the Obj-C ^ as a closure indicator. I think with existing Swift syntax, a lot of the confusion that caused www.fuckingblocksyntax.com would be avoided.

<snip>

makeIncrementer(forIncrement: ^(number: Int) {

   if (number == 3) { return true }

})

Why does the block now have to be declared within the function parameters?
Why do I have to declare the type of number now?
Why do I *not* have to declare the return type as being a boolean?
Would this syntax still support single expression implicit returns, e.g.
current Swift

> [1,2,3].map() { number in number == 3 }
$R0: [Bool] = 3 values {
  [0] = false
  [1] = false
  [2] = true
}

By having ^ mark an upcoming closure, I think it's a lot easier to follow what's going on with declarations because as you read left to right, you're prepped that a closure syntax is coming up as you read left to right. It also allows you to keep the params outside of the closure, which I think is a win. Closure params would also have the same syntax everywhere, and be extremely similar to normal method calls which would be easier for new Swift users.

There are three pieces at play here IMHO:
1. How functions (global and on types) are declared and implemented
2. How function specifications are indicated as types
3. How anonymous functions are declared and implemented

When I declare a global or type-specific function, I can specify its name
and arguments, assign external names to them (as well as internal names),
and declare my return value. The types, even if generic, must be known for
the function implementation to pass semantic checks.

The syntax for functions as types are really just a list of input and
output types. There are not (for instance) named parameter requirements on
the implementing functions. But the input and return types must be known to
declare a new function type.

Anonymous functions assume a great deal of type information from the
context in which they are used. This is why you can get away with not
declaring input types or even input names (using $0, $1, etc), the return
type or even if a value is returned. The code is mapped into the context it
is needed, then semantic evaluation is done.

You are attempting to change #2 and #3 at the same time, when really they
have quite different needs. I assume the reason anonymous functions (aka
closures in Swift) have their argument names on the inside of the block is
because the argument names have no external bearing whatsoever. Considering
I may or may not declare type information or even give all the parameters
names, this makes a certain kind of sense. If you were to move the argument
names outside the block, you would need a syntax specifically for that.
There are enough options that reusing the same base syntax between 1 and 3
or 2 and 3 would likely just _feel_ wrong.

There are enough rules about escape analysis to possibly make sense to
have function blocks, do blocks, repeat blocks, switch blocks, for…in
blocks, etc all be considered ‘kinds’ of blocks, and for closure blocks to
be on that list. It possibly makes sense for them to have a keyword just
like all the other blocks to simplify compiler and human visual parsing.
But I don’t envy the effort of redesigning and redecorating that particular
bike shed :slight_smile:

-DW


(Chris Lattner) #8

I can’t believe that you’re seriously considering use of ^ for closure syntax, it is one of the most hated aspects of ObjC’s blocks. :-) :-) FWIW, I’m the one to blame for the use of caret in ObjC’s block’s syntax. In my defense, this was necessary to fit blocks into the C grammar - everything everyone hates about blocks declaration syntax is shared with C function pointer syntax. That "problem to be solved” doesn’t exist in the Swift grammar, so I don’t see why we’d carry it over.

More generally, we are extremely unlikely to make a change to the basic declaration syntax of swift (e.g. func, var, etc) or closure expressions. They have been carefully considered, and work well in practice. I don’t see a reason to make a change here.

-Chris

···

On Dec 31, 2015, at 6:16 PM, Ethan Diamond via swift-evolution <swift-evolution@swift.org> wrote:

There are three pieces at play here IMHO:
1. How functions (global and on types) are declared and implemented
2. How function specifications are indicated as types
3. How anonymous functions are declared and implemented

Agreed, so lets flush out the rules a little more and see if we can find a common language for all three, since they're interconnected. For the sake of clarity, I'm going to refer to closures as blocks, since they're effectively the same thing. I think we should begin with these three rules:

1. Let's try to keep precedent for function calls, since blocks are pretty much functions
2. A ^ signifies that we are now entering a block context with regards to syntax
3. Just as in a lot of the rest of Swift, let's infer information we can, but optionally allow it to be respecified if it makes the syntax clearer


(Joe Bell) #9

+1 to not introduce ^ for closure syntax.

···

On Thu, Dec 31, 2015 at 11:26 PM, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

On Dec 31, 2015, at 6:16 PM, Ethan Diamond via swift-evolution < > swift-evolution@swift.org> wrote:

*There are three pieces at play here IMHO:*
*1. How functions (global and on types) are declared and implemented*
*2. How function specifications are indicated as types*
*3. How anonymous functions are declared and implemented*

Agreed, so lets flush out the rules a little more and see if we can find a
common language for all three, since they're interconnected. For the sake
of clarity, I'm going to refer to closures as blocks, since they're
effectively the same thing. I think we should begin with these three rules:

1. Let's try to keep precedent for function calls, since blocks are pretty
much functions
2. A ^ signifies that we are now entering a block context with regards to
syntax
3. Just as in a lot of the rest of Swift, let's infer information we can,
but optionally allow it to be respecified if it makes the syntax clearer

I can’t believe that you’re seriously considering use of ^ for closure
syntax, it is one of the most hated aspects of ObjC’s blocks. :slight_smile: :slight_smile:
FWIW, I’m the one to blame for the use of caret in ObjC’s block’s syntax.
In my defense, this was necessary to fit blocks into the C grammar -
everything everyone hates about blocks declaration syntax is shared with C
function pointer syntax. That "problem to be solved” doesn’t exist in the
Swift grammar, so I don’t see why we’d carry it over.

More generally, we are extremely unlikely to make a change to the basic
declaration syntax of swift (e.g. func, var, etc) or closure expressions.
They have been carefully considered, and work well in practice. I don’t
see a reason to make a change here.

-Chris

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


(Ethan Diamond) #10

FWIW I don't think the backlash to the use of ^ with Obj-C blocks was
because of the carat itself, but because of the inconsistency of the syntax
in different contexts. Sometimes the return type was after the ^, sometimes
before. Sometimes you had to use (^). Sometimes the carat had the name of
the block with it ^functionName. None of it fit in with the [object
methodName] syntax of the language itself.

However, none of those problems exist with block syntax in Swift - it would
be consistent all the way through. I guess what I'm getting at is that I
hope when Swift was being designed that the carat syntax wasn't considered
because of some benefit of the current syntax, and not from fear of the
initial backlash from people who didn't like Obj-C block syntax. I can't
speak to language design with respect to the compiler since I have no
experience in that area, but from a programming perspective, there are a
lot of benefits of block syntax over closure syntax and no benefits of
closures over block syntax.

- Block syntax clarifies some hard to read function declarations such as:

func makeIncrementer(forIncrement amount: Int) -> () -> Int"

- There is no other syntax in Swift where the body's parameters are named
inside the body. Everyone else has the ( inputs are named ) { body } syntax
- (some of these are implied):

  for (index in indices) { //Something with index }
  func doThing(index: Int) { //Something with index }
  if (let index = index) { //Something with index }

- You save the in keyword for for-in loops
- I showed earlier how it allows for parameter naming when defining a
closure type, which would be useful for autocompletion of blocks

Anyway, if closure syntax is unofficially set in stone then that's that. I
still appreciate you taking the time to listen.

-E

···

On Thu, Dec 31, 2015 at 9:26 PM, Chris Lattner <clattner@apple.com> wrote:

On Dec 31, 2015, at 6:16 PM, Ethan Diamond via swift-evolution < > swift-evolution@swift.org> wrote:

*There are three pieces at play here IMHO:*
*1. How functions (global and on types) are declared and implemented*
*2. How function specifications are indicated as types*
*3. How anonymous functions are declared and implemented*

Agreed, so lets flush out the rules a little more and see if we can find a
common language for all three, since they're interconnected. For the sake
of clarity, I'm going to refer to closures as blocks, since they're
effectively the same thing. I think we should begin with these three rules:

1. Let's try to keep precedent for function calls, since blocks are pretty
much functions
2. A ^ signifies that we are now entering a block context with regards to
syntax
3. Just as in a lot of the rest of Swift, let's infer information we can,
but optionally allow it to be respecified if it makes the syntax clearer

I can’t believe that you’re seriously considering use of ^ for closure
syntax, it is one of the most hated aspects of ObjC’s blocks. :slight_smile: :slight_smile:
FWIW, I’m the one to blame for the use of caret in ObjC’s block’s syntax.
In my defense, this was necessary to fit blocks into the C grammar -
everything everyone hates about blocks declaration syntax is shared with C
function pointer syntax. That "problem to be solved” doesn’t exist in the
Swift grammar, so I don’t see why we’d carry it over.

More generally, we are extremely unlikely to make a change to the basic
declaration syntax of swift (e.g. func, var, etc) or closure expressions.
They have been carefully considered, and work well in practice. I don’t
see a reason to make a change here.

-Chris


(Chris Lattner) #11

FWIW I don't think the backlash to the use of ^ with Obj-C blocks was because of the carat itself,

Fair enough, different people have different objections. I’m sure some people love blocks syntax :-)

but because of the inconsistency of the syntax in different contexts. Sometimes the return type was after the ^, sometimes before. Sometimes you had to use (^). Sometimes the carat had the name of the block with it ^functionName. None of it fit in with the [object methodName] syntax of the language itself.

No, it fit perfectly with C. Blocks are an extension to C, not technically an Objective-C extension.

Anyway, if closure syntax is unofficially set in stone then that's that. I still appreciate you taking the time to listen.

I understand the other points you make, but again, we’re pretty happy with closure syntax as is. Keeping the arguments inside the closure has a ton of advantages for the expression grammar (making it possible to parse :-) and allows trailing closures to emulate builtin statements.

-Chris

···

On Jan 1, 2016, at 2:21 PM, Ethan Diamond <edgewood7558@gmail.com> wrote:


(Paul Cantrell) #12

I’ll drop in a straight-up objection to the carat. The infix arrow syntax is perfectly clear, and in mathematical contexts has proved its readability since long before there were programming languages. Contra the proposal, there’s nothing wrong with this:

  func makeIncrementer(forIncrement amount: Int) -> () -> Int

The carat, on the other hand, is noise. Only habituation to Obj-C blocks makes it seem like anything else.

I _do_ agree that the { foo, bar in … } syntax is clumsy, both graphically and grammatically. Definitely not my favorite part of Swift. I don’t find the carat a compelling fix, however.

Cheers,

Paul

···

On Jan 1, 2016, at 4:21 PM, Ethan Diamond via swift-evolution <swift-evolution@swift.org> wrote:

FWIW I don't think the backlash to the use of ^ with Obj-C blocks was because of the carat itself, but because of the inconsistency of the syntax in different contexts. Sometimes the return type was after the ^, sometimes before. Sometimes you had to use (^). Sometimes the carat had the name of the block with it ^functionName. None of it fit in with the [object methodName] syntax of the language itself.


(Charles Srstka) #13

I wouldn’t say it fit perfectly. It matched C’s function pointer syntax, so it fit as well as that. However, C’s function pointer syntax is kind of horrible, so.

Not that that is the fault of anyone on the Objective-C team, of course.

Charles

···

On Jan 1, 2016, at 5:05 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 1, 2016, at 2:21 PM, Ethan Diamond <edgewood7558@gmail.com <mailto:edgewood7558@gmail.com>> wrote:

FWIW I don't think the backlash to the use of ^ with Obj-C blocks was because of the carat itself,

Fair enough, different people have different objections. I’m sure some people love blocks syntax :slight_smile:

but because of the inconsistency of the syntax in different contexts. Sometimes the return type was after the ^, sometimes before. Sometimes you had to use (^). Sometimes the carat had the name of the block with it ^functionName. None of it fit in with the [object methodName] syntax of the language itself.

No, it fit perfectly with C. Blocks are an extension to C, not technically an Objective-C extension.


(John Joyce) #14

In the context of C, it is as good as it can be, and actually a bit nicer than some function pointers are, though you can always C your way to something nicer looking which the typedefs generally do (or if the old preprocessor define macros).

···

On Jan 2, 2016, at 9:41 AM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 1, 2016, at 5:05 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 1, 2016, at 2:21 PM, Ethan Diamond <edgewood7558@gmail.com <mailto:edgewood7558@gmail.com>> wrote:

FWIW I don't think the backlash to the use of ^ with Obj-C blocks was because of the carat itself,

Fair enough, different people have different objections. I’m sure some people love blocks syntax :slight_smile:

but because of the inconsistency of the syntax in different contexts. Sometimes the return type was after the ^, sometimes before. Sometimes you had to use (^). Sometimes the carat had the name of the block with it ^functionName. None of it fit in with the [object methodName] syntax of the language itself.

No, it fit perfectly with C. Blocks are an extension to C, not technically an Objective-C extension.

I wouldn’t say it fit perfectly. It matched C’s function pointer syntax, so it fit as well as that. However, C’s function pointer syntax is kind of horrible, so.

Not that that is the fault of anyone on the Objective-C team, of course.

Charles