Syntactic Sugar in a Function Call
Trailing Lambda
The trailing lambda makes a function call look like the built-in syntax of the language, increasing the extensibility of the language.
When the last parameter of a function is the function type, and the argument corresponding to the function call is lambda, you can use the trailing lambda syntax to place lambda outside the parentheses () at the end of the function call.
The following example defines a myIf
function whose first parameter is of the Bool
type and second parameter is the function type. When the value of the first parameter is true
, the value of the second parameter is returned. Otherwise, 0
is returned. myIf
can be called in the same way as a common function or in trailing lambda mode.
func myIf(a: Bool, fn: () -> Int64) {
if(a) {
fn()
} else {
0
}
}
func test() {
myIf(true, { => 100 }) // General function call
myIf(true) { // Trailing closure call
100
}
}
If a function call has only one lambda as an argument, you can omit ()
and write only lambda.
Example:
func f(fn: (Int64) -> Int64) { fn(1) }
func test() {
f { i => i * i }
}
Flow Expressions
There are two types of flow operators: |>
infix operator (called pipeline
) that represents the data flow direction and ~>
infix operator (called composition
) that represents a combination of functions.
Pipeline Expressions
When you need to process the input data, you can use a pipeline
expression to simplify the description. The syntax of the pipeline
expression is e1 |> e2
. It is equivalent to the syntax sugar in the following format: let v = e1; e2(v)
.
e2
is the expression of the function type, and the type of e1
is the subtype of the parameter type of e2
.
Example:
func inc(x: Array<Int64>): Array<Int64> { // Increasing the value of each element in the array by '1'
let s = x.size
var i = 0
for (e in x where i < s) {
x[i] = e + 1
i++
}
x
}
func sum(y: Array<Int64>): Int64 { // Get the sum of elements in the array.
var s = 0
for (j in y) {
s += j
}
s
}
let arr: Array<Int64> = Array<Int64>([1, 3, 5])
let res = arr |> inc |> sum // res = 12
Composition Expressions
A composition
expression indicates a combination of two single-parameter functions. The syntax of the composition
expression is f ~> g
. It is equivalent to { x => g(f(x)) }
.
f
or g
is the expression of the function type with only one parameter.
If f
and g
are combined, the return type of f(x)
must be the subtype of the parameter type of g(...)
.
Example 1:
func f(x: Int64): Float64 {
Float64(x)
}
func g(x: Float64): Float64 {
x
}
var fg = f ~> g // The same as { x: Int64 => g(f(x)) }
Example 2:
func f(x: Int64): Float64 {
Float64(x)
}
let lambdaComp = ({x: Int64 => x}) ~> f // The same as { x: Int64 => f({x: Int64 => x}(x)) }
Example 3:
func h1<T>(x: T): T { x }
func h2<T>(x: T): T { x }
var hh = h1<Int64> ~> h2<Int64> // The same as { x: Int64 => h2<Int64>(h1<Int64>(x)) }
Note:
In the expression f ~> g, f is evaluated first, then g is evaluated, and finally the functions are combined.
In addition, flow operators and named parameter functions with no default values cannot be used together directly. This is because named parameter functions with no default values can be called only after the arguments of the named parameters are provided. The following is an example:
func f(a!: Int64): Unit {}
var a = 1 |> f // Error
If necessary, you can use a lambda expression to pass the argument of the named parameter of the f
function.
func f(a!: Int64): Unit {}
var x = 1 |> { x: Int64 => f(a: x) } // Ok
Likewise, when the parameter of f
has a default value, it is incorrect to use it together with a flow operator. The following is an example:
func f(a!: Int64 = 2): Unit {}
var a = 1 |> f // Error
However, when all named parameters have default values, a function can be called even though the arguments of the named parameters are not provided. Only non-named parameters need to be passed. Flow operators and functions that meet the preceding requirements can be used together. The following is an example:
func f(a: Int64, b!: Int64 = 2): Unit {}
var a = 1 |> f // Ok
If you want to pass other parameters to the parameter b
when calling f
, you also need to use a lambda expression.
func f(a: Int64, b!: Int64 = 2): Unit {}
var a = 1 |> {x: Int64 => f(x, b: 3)} // Ok
Variable-Length Parameters
A variable-length parameter is special syntactic sugar in a function call. When the last non-named parameter of a parameter is of the Array
type, a sequence of parameters can be directly passed to the position of the corresponding argument to replace the Array
literal (there can be no or multiple parameters). The following is an example:
func sum(arr: Array<Int64>) {
var total = 0
for (x in arr) {
total += x
}
return total
}
main() {
println(sum())
println(sum(1, 2, 3))
}
The result is as follows:
0
6
Note that only the last non-named parameter can be used as a variable-length parameter. This syntax sugar is not supported for named parameters.
func length(arr!: Array<Int64>) {
return arr.size
}
main() {
println(length()) // Error, expected 1 argument, found 0
println(length(1, 2, 3)) // Error, expected 1 argument, found 3
}
Variable-length parameters can be used in global functions, static member functions, instance member functions, local functions, constructors, function variables, lambda expressions, overloaded function call operators, and overloaded index operator function calls. They are not supported for the function calls of composition, pipeline, and other overloaded operators. The following is an example:
class Counter {
var total = 0
init(data: Array<Int64>) { total = data.size }
operator func ()(data: Array<Int64>) { total += data.size }
}
main() {
let counter = Counter(1, 2)
println(counter.total)
counter(3, 4, 5)
println(counter.total)
}
The result is as follows:
2
5
The function overloading resolution always preferentially considers the functions that can be matched without using variable-length parameters. Variable-length parameters are used for parsing only when no function can be matched. The following is an example:
func f<T>(x: T) where T <: ToString {
println("item: ${x}")
}
func f(arr: Array<Int64>) {
println("array: ${arr}")
}
main() {
f()
f(1)
f(1, 2)
}
The result is as follows:
array: []
item: 1
array: [1, 2]
If the compiler cannot make a resolution, the following error is reported:
func f(arr: Array<Int64>) { arr.size }
func f(first: Int64, arr: Array<Int64>) { first + arr.size }
main() {
println(f(1, 2, 3)) // Error
}