[Proposal] Change infix operator attributes syntax to be more consistent with the rest of the language


(Anton Zhilin) #1

First of all, I suggest that we remove `assignment` from Swift 2.2 in any
case. It has been deprecated for long enough.

Operator declarations are always global.
Operator declarations of the same operator are always in conflict, even if
in different modules.
Therefore, I believe, operators would be best defined using directive
syntax:

#operator(<>, fixity: infix, associativity: left, precedence: 100)
#operator(!, fixity: postfix)

It's obvious from this declaration that it must be global and must not be
duplicated even in different modules (remember C macros?).
It would allow us to remove operator declaration grammar entirely, add a
directive instead. Simplification of grammar and consistency is one of
directions for Swift 2.2 and Swift 3.0.

Why not curly braces? Curly braces in Swift declarations are used to
declare multiple "child" entities. On the other hand, attributes and
directives are used with *preudo-arguments*.
I also want to remind the main difference between attributes and directives
in Swift. @-attributes always stand before something and modify it.
#-directives are stand-alone things on themselves.

- Anton


(Brent Royal-Gordon) #2

First of all, I suggest that we remove `assignment` from Swift 2.2 in any case. It has been deprecated for long enough.

Operator declarations are always global.
Operator declarations of the same operator are always in conflict, even if in different modules.
Therefore, I believe, operators would be best defined using directive syntax:

#operator(<>, fixity: infix, associativity: left, precedence: 100)
#operator(!, fixity: postfix)

It's obvious from this declaration that it must be global and must not be duplicated even in different modules (remember C macros?).
It would allow us to remove operator declaration grammar entirely, add a directive instead. Simplification of grammar and consistency is one of directions for Swift 2.2 and Swift 3.0.

Why not curly braces? Curly braces in Swift declarations are used to declare multiple "child" entities. On the other hand, attributes and directives are used with *preudo-arguments*.
I also want to remind the main difference between attributes and directives in Swift. @-attributes always stand before something and modify it. #-directives are stand-alone things on themselves.

Treating it as a compiler directive is an interesting idea. It certainly doesn't work anything like other declarations in Swift; a compiler directive might drive that point home.

I don't really like this specific design, though—it doesn't really match the look and feel of existing directives. Looking through the Swift book, I believe the only compiler directive that actually *changes* anything is `#setline` (The Directive Formerly Known As `#line`). Its parameters consist of an unmarked list of values with no separator or labeling. Trying to duplicate that style would give us something like this:

  #operator <> infix left 100

That's, um, pretty bad. There *is* precedent for function-like constructs, though, so perhaps we could do this:

  #operator <> infix(associativity: left, precedence: 100)
  #operator ! prefix

That's actually not too bad—it connects the attributes very strongly to the `infix` keyword while still putting those details at the end of the statement so they don't obscure the more important bits.

One other issue is that the compiler directives which are permitted in statement position (rather than expressions like `#line` or `#available(...)`) are "verb-y"—they read as commands, not nouns. It would probably be more consistent to use a verb of some kind:

  #addoperator <> infix(associativity: left, precedence: 100)
  #defoperator <> infix(associativity: left, precedence: 100)

A space would make that more readable (even if it would break consistency with `#setline`:

  #add operator <> infix(associativity: left, precedence: 100)
  #def operator <> infix(associativity: left, precedence: 100)

Or, more fancifully:

  #invent operator <> infix(associativity: left, precedence: 100)
  #adopt operator <> infix(associativity: left, precedence: 100)

In theory, this could become a mechanism to extend the grammar in other ways, like:

  // add `foo?.bar()` to grammar
  #invent dispatcher ?. infix

  // add `if cond { statements } else { statements }` to grammar
  #invent statement if controlflow(_: expression, _: block, else: block)

But that's a whole different kind of fanciful.

···

--
Brent Royal-Gordon
Architechies


(Jean-Daniel) #3

As describe in the #setline change proposition, #setline is a tool generated directive used only by the compiler. No time has been waste to try to design it to be ‘swift like’ or readable, or anything else, and so I don’t think we should use it as an example in any design discussion.

···

Le 6 mars 2016 à 12:44, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

First of all, I suggest that we remove `assignment` from Swift 2.2 in any case. It has been deprecated for long enough.

Operator declarations are always global.
Operator declarations of the same operator are always in conflict, even if in different modules.
Therefore, I believe, operators would be best defined using directive syntax:

#operator(<>, fixity: infix, associativity: left, precedence: 100)
#operator(!, fixity: postfix)

It's obvious from this declaration that it must be global and must not be duplicated even in different modules (remember C macros?).
It would allow us to remove operator declaration grammar entirely, add a directive instead. Simplification of grammar and consistency is one of directions for Swift 2.2 and Swift 3.0.

Why not curly braces? Curly braces in Swift declarations are used to declare multiple "child" entities. On the other hand, attributes and directives are used with *preudo-arguments*.
I also want to remind the main difference between attributes and directives in Swift. @-attributes always stand before something and modify it. #-directives are stand-alone things on themselves.

Treating it as a compiler directive is an interesting idea. It certainly doesn't work anything like other declarations in Swift; a compiler directive might drive that point home.

I don't really like this specific design, though—it doesn't really match the look and feel of existing directives. Looking through the Swift book, I believe the only compiler directive that actually *changes* anything is `#setline` (The Directive Formerly Known As `#line`). Its parameters consist of an unmarked list of values with no separator or labeling. Trying to duplicate that style would give us something like this:


(Anton Zhilin) #4

I don't really like this specific design, though—it doesn't really match

the look and feel of existing directives.

I propose that all compiler directives (that have arguments) be formatted
in pseudo-function form:
#setline(1, file: "main.swift")
I'll create a proposal for this, which will go hand-to-hand with my syntax
for operators.

Additionally, I've come up with an idea for complete operators overhaul.
The main suggestions are as follows:

1.
All operators are available without any pre-definition:

postfix func ! <T>(left: T?) -> T

func <>(left: Int, right: Int) -> Bool // assuming infix<>, no
associativity, no precedence

1 ||| 2 // error: function |||(Int, Int) is not defined

2.
If we want to globally set associativity or precedence for infix operators,
we write:

#operator(|||, associativity: left, precedence: 100)

These operator configuration directives must not be in conflict. We can
omit one or both of parameters:

#operator(|||, precedence: 100)
#operator(|||) // just disallow other #operator directives

···

2016-03-06 14:44 GMT+03:00 Brent Royal-Gordon <brent@architechies.com>:

> First of all, I suggest that we remove `assignment` from Swift 2.2 in
any case. It has been deprecated for long enough.
>
> Operator declarations are always global.
> Operator declarations of the same operator are always in conflict, even
if in different modules.
> Therefore, I believe, operators would be best defined using directive
syntax:
>
> #operator(<>, fixity: infix, associativity: left, precedence: 100)
> #operator(!, fixity: postfix)
>
> It's obvious from this declaration that it must be global and must not
be duplicated even in different modules (remember C macros?).
> It would allow us to remove operator declaration grammar entirely, add a
directive instead. Simplification of grammar and consistency is one of
directions for Swift 2.2 and Swift 3.0.
>
> Why not curly braces? Curly braces in Swift declarations are used to
declare multiple "child" entities. On the other hand, attributes and
directives are used with *preudo-arguments*.
> I also want to remind the main difference between attributes and
directives in Swift. @-attributes always stand before something and modify
it. #-directives are stand-alone things on themselves.

Treating it as a compiler directive is an interesting idea. It certainly
doesn't work anything like other declarations in Swift; a compiler
directive might drive that point home.

I don't really like this specific design, though—it doesn't really match
the look and feel of existing directives. Looking through the Swift book, I
believe the only compiler directive that actually *changes* anything is
`#setline` (The Directive Formerly Known As `#line`). Its parameters
consist of an unmarked list of values with no separator or labeling. Trying
to duplicate that style would give us something like this:

        #operator <> infix left 100

That's, um, pretty bad. There *is* precedent for function-like constructs,
though, so perhaps we could do this:

        #operator <> infix(associativity: left, precedence: 100)
        #operator ! prefix

That's actually not too bad—it connects the attributes very strongly to
the `infix` keyword while still putting those details at the end of the
statement so they don't obscure the more important bits.

One other issue is that the compiler directives which are permitted in
statement position (rather than expressions like `#line` or
`#available(...)`) are "verb-y"—they read as commands, not nouns. It would
probably be more consistent to use a verb of some kind:

        #addoperator <> infix(associativity: left, precedence: 100)
        #defoperator <> infix(associativity: left, precedence: 100)

A space would make that more readable (even if it would break consistency
with `#setline`:

        #add operator <> infix(associativity: left, precedence: 100)
        #def operator <> infix(associativity: left, precedence: 100)

Or, more fancifully:

        #invent operator <> infix(associativity: left, precedence: 100)
        #adopt operator <> infix(associativity: left, precedence: 100)

In theory, this could become a mechanism to extend the grammar in other
ways, like:

        // add `foo?.bar()` to grammar
        #invent dispatcher ?. infix

        // add `if cond { statements } else { statements }` to grammar
        #invent statement if controlflow(_: expression, _: block, else:
block)

But that's a whole different kind of fanciful.

--
Brent Royal-Gordon
Architechies


(Kevin Lundberg) #5

> I don't really like this specific design, though—it doesn't really
match the look and feel of existing directives.

I propose that all compiler directives (that have arguments) be
formatted in pseudo-function form:
#setline(1, file: "main.swift")
I'll create a proposal for this, which will go hand-to-hand with my
syntax for operators.

Additionally, I've come up with an idea for complete operators
overhaul. The main suggestions are as follows:

If you intend to draft a proposal for this that encompasses operators,
I'll happily hold off. My gut feeling though is that a proposal that
standardizes directives and also changes how operators are declared may
be better served if they were split into two distinct proposals.

1.
All operators are available without any pre-definition:

postfix func ! <T>(left: T?) -> T

func <>(left: Int, right: Int) -> Bool // assuming infix<>, no
associativity, no precedence

1 ||| 2 // error: function |||(Int, Int) is not defined

This is interesting, but I wonder if an infix operator function with
more esoteric operator characters would be easily identifiable as an
operator vs a normal function. Operator declarations don't need to be
near their function definitions though, and this is technically possible
to do today anyways, so maybe my concern with this change is unfounded.
One way to make this be 100% clear to the reader though would be to
require infix in front of func, just like prefix and postfix which I
kind of like.

2.
If we want to globally set associativity or precedence for infix
operators, we write:

#operator(|||, associativity: left, precedence: 100)

These operator configuration directives must not be in conflict. We
can omit one or both of parameters:

#operator(|||, precedence: 100)
#operator(|||) // just disallow other #operator directives

I agree that the compiler directive look is interesting (though it's not
my most preferred method of declaring operators aesthetically). However,
omitting fixity here, as this example does, could constrain the design
of future changes that might add behavior control to prefix and postix
operators, and in my opinion it's a better idea to leave that avenue open.