Custom Operators¶
CDot allows you to define custom overloads of the available operators, as well as entirely new ones. Operators are declared like normal functions, but with a special syntax for the function name.
Overloading Existing Operators¶
Existing operators like +
or -
can be implemented for new types easily. Simply define a global function with the operator name in place of a normal function name. For an infix function, the declaration needs to take exactly two parameters, for a prefix or postfix function, only one parameter is allowed.
1 2 3 4 5 6 7 8 9 10 11 | /// Remove the right-hand side string from the left one.
def infix -(_ str: String, _ rem: String) -> String {
if str.endsWith(rem) {
return str[0..(str.size - rem.size)]
}
return str
}
let s1 = "Hello, World!"
print(s1 - ", World!") // Prints "Hello"
|
Defining New Operators¶
If you want to define an operator that does not yet exist in the language, you need to declare it first. An operator declaration starts with one of the keywords infix
, prefix
, or postfix
, followed by the operator name and an optional precedence group. Suppose we want to create the familiar increment and decrement operators from C
. First, we have to declare the operators themselves:
1 2 3 4 | prefix ++
postfix ++
prefix --
postfix --
|
Once we’ve done that, we can define our custom operator functions:
1 2 3 4 5 6 7 8 9 10 11 12 | def prefix ++(_ value: mut ref Int) -> Int {
value += 1
return value
}
def postfix ++(_ value: mut ref Int) -> Int {
var oldVal = value
value += 1
return oldVal
}
// And so on...
|
Note
You may have noticed the mut ref
before the parameter type in the function signatures. This is required because this function needs to mutate the parameter it is passed. You can learn more about this in a different chapter.
Infix operators can also define a precedence group, which will decide how the operator will be parsed. You may remember from school that multiplication has a higher precedence than addition, which is reflected by the following precedence group declarations in the standard library:
1 2 3 4 5 6 7 8 9 10 11 12 13 | precedenceGroup AdditionPrecedence {
associativity: left
higherThan: // Some other precedence group
}
precedenceGroup MultiplicationPrecedence {
associativity: left
higherThan: AdditionPrecedence
}
// Now we can declare the operators like so...
infix + : AdditionPrecedence
infix * : MultiplicationPrecedence
|
Precedence groups are related to each other with higherThan
and lowerThan
relationships. If an operator belongs to a precedence group that is higher than another, it will be parsed first, which means that the following two expressions are equivalent:
_ = 3 + 4 * 5
_ = 3 + (4 * 5) // equivalent - `*` binds more tightly than `+`
If no relationship is established between two precedence groups, you are required to use parentheses to disambiguate.
Note
There are two more attributes you can define for a precedence group besides higherThan
and lowerThan
. The first is the associativity
, which can be either left
or right
(default is left
). The associativity of an operator specifies how a sequence of operators with the same precedence level are grouped together in the absence of grouping parentheses. For example, the -
operator is left-associative, which means that the expression 3 - 4 - 5
is equivalent to (3 - 4) - 5
, which equals -12. If it were right associative, it would be parsed as 3 - (4 - 5)
, which would equal 4. The second is assignment
, which can be true
or false
(default is false
).