As we know, you can pass operators directly to functions like this:
func foo(_ x: (Int, Int) -> Int) {
}
foo(+)
However, when I tried to do this in an assignment context, it doesn't work:
let f: (Int, Int) -> Int = +
It gives two errors:
Expected initial value after '='
Unary operator cannot be separated from its operand
I worked out that I have to do:
let f: (Int, Int) -> Int = (+)
How exactly does Swift parse each of these cases, allowing me to omit brackets in one case, but not the other?
Note that I'm not asking about the rationale behind Swift being designed this way. I'm just asking about the grammar production rules that produces this behaviour.
On Swift.org, I've been able to find this production rule that says operators are function call arguments:
function-call-argument → operator | identifier ':' operator
However, I've not been able to find production rules for (+). I started with "parenthesised expressions", which is a pair of parentheses surrounding an "expression". but "operator" is apparently not a kind of "expression".
I found Swift AST Explorer, which allows me to inspect the AST of Swift code. From its GitHib page, it seems to use the same library that the Swift compiler uses to parse Swift code (lib/Syntax).
Using Swift AST Explorer, the (+) part of let x: (Int, Int) -> Int = (+) parses to:
TupleExpr
(
TupleExprElementList
TupleExprElement
IdentifierExpr
+
)
So apparently, (+) here is a 1-tuple! And + is classified as an "identifier expression". This seems to also explain why operators in tuple elements don't need parentheses:
let f: ((Int, Int) -> Int, (Bool) -> Bool, (Int) -> Int) = (+, !, -)
After a bit more searching, I found ExprNodes.py, which seems to be one of the files from which some of the APIs in lib/Syntax is generated. In there, I saw that SpacedBinaryOperatorToken is a child of IdentifierExpr, so that's probably what + is being parsed as.
Node('IdentifierExpr', kind='Expr',
children=[
Child('Identifier', kind='Token',
token_choices=[
'IdentifierToken',
'SelfToken',
'CapitalSelfToken',
'DollarIdentifierToken',
'SpacedBinaryOperatorToken',
]),
Child('DeclNameArguments', kind='DeclNameArguments',
is_optional=True),
]),
Interestingly, unary operators such as ! and - are also being parsed as "binary operators".
the "+" is an infix operator, therefore I guess it has to be "between" some defined types (eg. Int and Int), and you cannot just declare it standing alone. You can define your own infix operator, lets say with this code:
infix operator ~
func ~(lhs: Double, rhs: Double) -> Double {
return lhs * lhs + rhs * rhs
}
let val1: Double = 2
let val2: Double = 3
let squareSum = val1 ~ val2
print(squareSum)
So I guess the key to your answer is that "+" is not just some regular function, but an infix operator and defining it with parenthesis (x) helps the compiler to understand it does not stand in between something. Hopefully this answer helps you with some guideline where to look for further explanation if you wish.
i tried this
simple2 = {s1, s2 in s1 > s2}
and
var simple2 = {$0 > $1}
but still showing me
swift 3 closure Ambiguous use of 'operator >'
The closure must explicitly declare the type of the s1 and s2 parameters and that type must implement > operator. The typical way to do that is to make the signature of that closure ensure that the two parameters are (a) the same type; and (b) conform to the Comparable protocol.
If you want simple2 to take any Comparable type, rather than a closure, you could define a generic function:
func simple2<T: Comparable>(_ s1: T, _ s2: T) -> Bool {
return s1 > s2
}
Then you could call it with any Comparable type.
You need to specify the types of s1 and s2 and $0 and $1. Not even a human can infer what type you want these to be of, let alone the Swift compiler.
> can be applied to multiple types. Here are some of the examples:
Int and Int
Double and Double
CGFloat and CGFloat
You can specify the types like this:
let simple2: (Int, Int) -> Bool = {$0 > $1}
The following piece of code are erroneous in Swift.
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {$0}))
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {return $0}))
The error given by XCode playground is Cannot convert value of type '(Int, Int)' to closure result type 'Int'.
While the following pieces of code are completely fine.
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {$0 + $1}))
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {$1; return $0}))
func foo(closure: (Int, Int) -> Int) -> Int {
return closure(1, 2)
}
print(foo(closure: {a, b in a}))
It seems that in a situation where arguments to a closure are referred to by shorthand argument names, they must be used exhaustively if the the body of the closure only consists of the return expression. Why?
If you just use $0, the closure arguments are assumed to be a tuple instead of multiple variables $0, $1 etc. So you should be able to work around this by extracting the first value of that tuple:
print(foo(closure: {$0.0}))
Your "why" is like asking "why is an American football field 100 yards long?" It's because those are the rules. An anonymous function body that takes parameters must explicitly acknowledge all parameters. It can do this in any of three ways:
Represent them using $0, $1, ... notation.
Represent them using parameter names in an in line.
Explicitly discard them by using _ in an in line.
So, let's take a much simpler example than yours:
func f(_ ff:(Int)->(Void)) {}
As you can see, the function f takes one parameter, which is a function taking one parameter.
Well then, let's try handing some anonymous functions to f.
This is legal because we name the parameter in an in line:
f {
myParam in
}
And this is legal because we accept the parameter using $0 notation:
f {
$0
}
And this is legal because we explicitly throw away the parameter using _ in the in line:
f {
_ in
}
But this is not legal:
f {
1 // error: contextual type for closure argument list expects 1 argument,
// which cannot be implicitly ignored
}
In Swift, operators are declared as functions. Does it mean they can also be used as callback functions? If so, how? If not, why?
Idea is, depending on the values of two different numbers, apply different operators to them.
In Swift functions are first class types, you can pass them as arguments to other functions expecting them.
A callback is nothing more than a parameter of another function.
Putting this all together means that yes, you can use an operator as a callback, as long as its type is compatible.
For example you can do:
[1, 2, 3, 4].reduce(0, combine: +)
// => 10
That is possible because + has type (Int, Int) -> Int, which matches the expected type for the combine: parameter of reduce when called on an array of Int.
Another example:
func foo(a: Int, f: (Int, Int) -> Int) -> Int {
return { a, f in
return f(a, 42)
}
}
foo(1, -)
// => 41
Is it possible to pass in a tuple into a function as long as their types match up?
When I try it, I get a missing argument in parameter error:
var myTuple = ("Text",10,"More Text")
func myFunction(a:String, b:Int, c:String) {
// etc...
}
myFunction(myTuple)
It was possible, although was deprecated in Swift 2.2:
In Swift 2.1 and earlier it was possible to use a carefully crafted tuple to fill the parameters of a function. So, if you had a function that took two parameters, you could call it with a two-element tuple as long as the tuple had the correct types and element names.
...
This syntax — affectionately called “tuple splat syntax” — is the antithesis of idiomatic Swift’s self-documenting, readable style, and so it’s deprecated in Swift 2.2.
https://swift.org/blog/swift-2-2-new-features/
I came here wanting to know how to pass a tuple as a function parameter. The answers here focus on a different case. I'm not entirely clear what the OP was after.
In any case, here is how to pass a tuple as a parameter. And, for good measure, how to do it variadically.
func acceptTuple(tuple : (Int, String)) {
print("The Int is: \(tuple.0)")
print("The String is '\(tuple.1)'")
}
acceptTuple((45, "zebras"))
// Outputs:
// The Int is: 45
// The String is 'zebras'
func acceptTuples(tuples : (Int, String) ...) {
var index = 0
// note: you can't use the (index, tuple) pattern in the for loop,
// the compiler thinks you're trying to unpack the tuple, hence
/// use of a manual index
for tuple in tuples {
print("[\(index)] - Int is: \(tuple.0)")
print("[\(index)] - String is '\(tuple.1)'")
index++
}
}
acceptTuples((45, "zebras"), (17, "armadillos"), (12, "caterpillars"))
//Outputs
//[0] - Int is: 45
//[0] - String is 'zebras'
//[1] - Int is: 17
//[1] - String is 'armadillos'
//[2] - Int is: 12
//[2] - String is 'caterpillars'
Passing tuples in can be a quick and convenient approach, saving you from having to create wrappers etc. For example, I have a use case where I am passing a set of tokens and parameters to create a game level. Tuples makes this nice and compact:
// function signature
class func makeLevel(target: String, tokens: (TokenType, String)...) -> GameLevel
// The function is in the class Level. TokenType here is an Enum.
// example use:
let level = Level("Zoo Station", tokens:
(.Label, "Zebra"),
(.Bat, "LeftShape"),
(.RayTube, "HighPowered"),
(.Bat, "RightShape"),
(.GravityWell, "4"),
(.Accelerator, "Alpha"))
Yes, it's possible under these conditions:
the tuple must be immutable
the number of values in the tuple, their type, and their order must match the parameters expected by the function
named parameters must match external names in the function signature
non-named parameters must match parameters without external name in the function signature
So, your code is ok, the only thing you have to do is turning the tuple into an immutable one (i.e. using let and not var):
let myTuple = ("Text", 10, "More Text")
func myFunction(a:String, b:Int, c:String) {
// etc...
}
myFunction(myTuple)
One more example with external names:
let myTuple = ("Text", paramB: 10, paramC: "More Text")
func myFunction(a:String, paramB b:Int, paramC c:String) {
// etc...
}
myFunction(myTuple)
In your tuple, it appears as though you must name them and then refer to them as such:
so your code should be
var myTuple = (val1: "Text", val2: 10, val3: "More Text")
func myFunction(a:String, b:Int, c:String) {
// etc...
}
myFunction(myTuple.val1, myTuple.val2, myTuple.val3)
The tuple has named values (val1, val2, val3) which you set and then reference, when you pass in myTuple, to the function myFunction(), it appears as though you are just filling 1 of the 3 available arguements - and with the wrong type to boot! This is the equivalent of storing the types in a tuple, then taking them out for a function call. However, if you want a function to actually take a tuple as a parameter, see below:
var myTuple = (val1: "Text", val2: 10, val3: "More Text")
func tupleFunc(a:(String, Int, String)) {
}
tupleFunc(myTuple)
Yes, but that's the wrong structure: you're passing three variables called a, b, and c rather than a tuple with those components.
You need parentheses around the whole thing:
var myTuple = ("Text", 10, "More Text")
func myFunction(a:(x: String, y: Int, z: String)) {
println(a)
}
myFunction(myTuple)
You can use the following feature: Swift allows you to pass a function (f1) with any number of parameters (but without inout parameters) as a parameter of type (TIn) -> TOut to another function. In this case, TIn will represent a tuple from the parameters of the function f1:
precedencegroup ApplyArgumentPrecedence {
higherThan: BitwiseShiftPrecedence
}
infix operator <- :ApplyArgumentPrecedence
func <-<TIn, TOut>(f: ((TIn) -> TOut), arg: TIn) -> TOut {
return f(arg)
}
func sum(_ a: Int, _ b: Int) -> Int {
return a + b
}
print(sum <- (40, 2))
In swift 3.0, we should not able to pass the tuple directly to the function.If we did so, it shows the error message as "This type has been removed in swift 3.0"
func sum(x: Int, y: Int) -> Int
return x+y }
let params = (x: 1, y: 1)
let x = params.0
let y = params.1
sum(x: x, y: y)
Hope it helps you!!
The best option for now seems to be to just save it to a compound variable or use the build in dot syntax
let (val1, val2) = (1, 2)
func f(first: Int, second: Int) { }
f(first: val1, second: val2)
let vals = (1, 2)
f(first: vals.0, second: vals.1)
That feature called implicit tuple splat was removed in swift 3.
You can find more detailed explanation on the removal proposal here
Some suggestions to keep using tuple as an argument is by doing so:
func f1(_ a : (Int, Int)) { ... }
let x = (1, 2)
f1(x)
func f2<T>(_ a : T) -> T { ... }
let x = (1, 2)
f2(x)