Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Precedence group higher than closure

Tags:

swift

I'm trying to define a custom operator in Swift with a precedence group higher than a closure. In particular, I want to be able to write:

foo --> bar { 
    //... 
}

The --> operator returns a function that takes a closure of type () -> Void as it's sole parameter.

However, I have only been able to get

(foo --> bar) { 
    //... 
} 

to work. Is there an operator precedence that can make this work without parentheses?

Here's the precedence group for

precedencegroup LongArrowPrecedence {
    associativity: left
    higherThan: AssignmentPrecedence
}

infix operator --> : LongArrowPrecedence

Thanks!

like image 940
James Eagan Avatar asked Jan 18 '17 15:01

James Eagan


1 Answers

We first set up a complete and verifiable example:

precedencegroup LongArrowPrecedence {
    associativity: left
    higherThan: AssignmentPrecedence
}

infix operator --> : LongArrowPrecedence

func -->(lhs: Int, rhs: Int) -> (() -> ()) -> () {
    return { print(lhs+rhs, terminator: ""); $0() }
}

As well as examples of the paranthesis-embraced valid calls using this operator, immediately followed by a call to the closure which --> returns.

let foo = 1
let bar = 2

// OK
(foo --> bar) { 
    print(" is the magic number")
} // 3 is the magic number

// OK
((-->)(foo, bar)) { 
    print(" is the magic number")
} // 3 is the magic number

This doesn't tell us much, but if we study the following failing cases

// ERROR: cannot call value of non-function type 'Int'
foo --> bar { 
    print(" is the magic number")
} // 3 is the magic number

// ... equivalent to
// ERROR: cannot call value of non-function type 'Int'
foo --> bar({ 
    print(" is the magic number")
}) // 3 is the magic number

We realize that the issue here is not "precedence lower than a closure", but rather that a function-call-argument-clause (a set of parantheses following any postfix-expression­) will attempt a call to that postfix-expression­, as if the postfix-expression was a method/function/closure. If the postfix-expression is not callable, or if the call within the function-call-argument-clause does not match any overload of the callable, then the compiler will produce an error.

42()           // ERROR: cannot call value of non-function type 'Int'
let foo = 42
foo()          // ERROR: cannot call value of non-function type 'Int'

func bar() {}  // ERROR: argument passed to call that takes no arguments
bar(42)

Hence, the trailing closure supplied to the closure returned from --> is not relevant here: it's simply an argument to the returned closure, whereas the key issue is that Swift will apply a function-call-argument-clause to the postfix-expression­ which immediately precedes the clause. In you example, bar constitutes that postfix expression, and only if you wrap foo --> bar in parantheses will the combined wrapped expression constitute the postfix-expression­ onto which the following function-call-argument-clause is applied.

Postfix Expressions

Postfix expressions are formed by applying a postfix operator or other postfix syntax to an expression. Syntactically, every primary expression is also a postfix expression.

Primary Expressions

Primary expressions are the most basic kind of expression. They can be used as expressions on their own, and they can be combined with other tokens to make prefix expressions, binary expressions, and postfix expressions.

You will not be able to circumvent this, as operator precedence is not applicable to the function-call-argument-clause; the latter (and its "precedence") is defined by the grammar of a function call expression.

like image 123
dfrib Avatar answered Oct 24 '22 11:10

dfrib