The original link: www.raywenderlich.com/4018226-ove…

Swift overload custom operator

In this Swift tutorial, you’ll learn how to create custom operators, overload existing operators, and set operator priorities.

Operators are the core building blocks of any programming language. Can you imagine programming without using + or =? Operators are so basic that most languages treat them as part of a compiler (or interpreter). However, the Swift compiler does not hardcode most operators, but rather provides the library with methods to create the operators. It leaves the Swift standard library to provide all the common operators you’d expect. This subtle difference opens the door to huge customization potential.

Swift operators are particularly powerful because you can change them to suit your needs in two ways: assigning new functionality to existing operators (called operator overloading), and creating new custom operators. In this tutorial, you’ll use a simple Vector structure and build your own set of operators to combine different vectors.

An introduction to

Open Xcode and create a Playground by File▶New▶Playground. Select the blank template and name your Playground CustomOperators. Remove all default code so that you can start with a blank SLATE.

Add the following code to your Playground:

struct Vector {
  let x: Int
  let y: Int
  let z: Int
}

extension Vector: ExpressibleByArrayLiteral {
  init(arrayLiteral: Int...). {assert(arrayLiteral.count= =3."Must initialize vector with 3 values.")
    self.x = arrayLiteral[0]
    self.y = arrayLiteral[1]
    self.z = arrayLiteral[2]}}extension Vector: CustomStringConvertible {
  var description: String {
    return "(\(x).\(y).\(z))"}}Copy the code

Here, you can define a Vector with three properties that follow two protocols. The CustomStringConvertible protocol and the Description evaluation property allow you to print a friendly string to represent a Vector.

At the bottom of the playground, add the following code:

let vectorA: Vector = [1.3.2]
let vectorB = [-2.5.1] as Vector
Copy the code

You just used two arrays to create two vectors without initializers! How does that happen? ExpressibleByArrayLiteral agreement provides a smooth interface to initialize the Vector. The protocol requires an unusable initializer with mutable parameters: init (arrayLiteral: Int…)

The mutable argument arrayLiteral allows you to pass in an unlimited number of values separated by commas. For example, you could create a Vector like this: Vector(arrayLiteral: 0) or Vector(arrayLiteral: 5,4,3).

This protocol is further convenient and allows you to initialize directly with arrays, as long as you clearly define the type, which is what you do for vectorA and vectorB.

The only caveat with this approach is that you must accept arrays of any length. If you put this code into your application, remember that if you pass in an array of any length other than three, it will crash. If you try to initialize a Vector with fewer or more than three values, the assertion at the top of the initializer will alert you in the console during development and internal testing.

Individual vectors are good, but it’s even better if you can do things with them. Just as you did in school, you will begin your journey of learning addition.

Overload the addition operator

A simple example of operator overloading is the addition operator. If you use it with two numbers, the following happens:

1 + 1 / / 2
Copy the code

However, if the same addition operator is used on a string, it behaves quite differently:

"1" + "1" / / "11"
Copy the code

When + is used with two integers, it adds them in arithmetic form. But when it is used with two strings, it concatenates them.

In order for an operator to be overloaded, you must implement a function named operator symbol.

Note: You can define overloaded functions as class methods, which you will do in this tutorial. To do so, it must be declared static so that it can be accessed without defining an instance of it.

Add the following code to the end of the playground:

// MARK: - Operators
extension Vector {
  static func + (left: Vector, right: Vector) -> Vector {
    return [
      left.x + right.x,
      left.y + right.y,
      left.z + right.z
    ]
  }
}

Copy the code

This function takes two vectors as arguments and returns their sum as a new vector. In order to add vectors, you just add the variables that make up the vectors.

To test this feature, add the following to the bottom of the playground:

vectorA + vectorB // (-1, 8, 3)
Copy the code

You can see the result of adding the vectors in the right-hand sidebar of playground.

Other types of operators

The addition operator is what is called an INFX operator, meaning that it is used between two different values. There are other types of operators:

  • infix: Used between two values, such as the addition operator (for example, 1 + 1)
  • prefix: To be added before a value, such as the minus operator (e.g. -3).
  • postfix: added after the value, such as force-unpack operators (for example, mayBeNil!)
  • ternary: Inserts two symbols between three values. In Swift, there is no support for user-defined ternary operators, only one built-in ternary operator that you can use inRead in Apple’s documentation. The next operator you want to overload is the minus sign, which changesVectorThe symbol of each variable of. For example, if it is applied tovectorA, i.e.,31 (1), the return(-1, -3, -2).

Add the following code to the previous static function in this extension:

static prefix func - (vector: Vector) -> Vector {
  return [-vector.x, -vector.y, -vector.z]
}
Copy the code

Operators are of type Infix by default, but if you want the operator to be of another type, you need to specify the operator type in the function declaration. The minus operator is not of type Infix, so you add the prefix modifier to the function declaration.

At the bottom of the playground, add the following code:

-vectorA / / (1, 3, 2)
Copy the code

Check that the result is correct in the sidebar.

Next is subtraction, which I’ll leave to you to realize yourself. When you’re done, check to make sure your code is similar to mine. Tip: Subtracting is the same as adding a negative number.

Give it a try and check out the solutions below if you need help!

static func - (left: Vector, right: Vector) -> Vector {
  return left + -right
}
Copy the code

Test the output of your new operator by adding the following code to the bottom of the playground:

vectorA - vectorB // (3, -2, 1)
Copy the code

Different types of parameters? No problem!

You can also multiply vectors by numbers by scalar multiplication. To multiply a vector by 2, we can multiply each component by 2. You’re going to implement this.

One thing you need to consider is the order of the arguments. When you do addition, the order doesn’t matter, because both arguments are vectors.

For scalar multiplication, you need to consider Int * Vector and Vector * Int. If you implement only one of these cases, the Swift compiler will not automatically know that you want it to work in the other order.

To implement scalar multiplication, add the following two functions under the subtraction function you just added:

static func * (left: Int, right: Vector) -> Vector {
  return [
    right.x * left.right.y * left.right.z * left]}// The overloaded * operator is used here
static func * (left: Vector, right: Int) -> Vector {
  return right * left
}
Copy the code

To avoid writing the same code multiple times, the second function simply forwards its arguments to the first.

In mathematics, vectors have another interesting operation, called the cross product. Cross products are beyond the scope of this tutorial, but you can learn more about them on the Cross Product Wikipedia page.

Since the use of custom symbols is discouraged in most cases (who wants to open the emoji menu while coding?) , it is very convenient to use the asterisk repeatedly for cross product operation.

Unlike scalar multiplication, the cross product takes two vectors as arguments and returns a new vector.

Add the following code to implement the cross product after the multiplication function you just added:

static func * (left: Vector, right: Vector) -> Vector {
  return [
    left.y * right.z - left.z * right.y,
    left.z * right.x - left.x * right.z,
    left.x * right.y - left.y * right.x
  ]
}

Copy the code

Now add the following calculation to the bottom of the playground, using both the multiplication and cross operators:

vectorA * 2 * vectorB / / (- 14, to 10, 22)
Copy the code

This code finds the scalar multiplication of a vectorA and 2, and then finds the cross product of that vector with a vectorB. Note that the asterisk operator is always left to right, so the previous code is the same as using parentheses to group operations, such as (vectorA * 2) * vectorB.

Operator protocol

Some protocols require operators to be implemented. For example, types that match Equatable must implement the == operator. Similarly, types that are Comparable must at least implement < and ==, because Comparable inherits from Equatable. The Comparable types can also optionally implement >, >=, and <=, but these operators have default implementations.

Comparable doesn’t mean much for Vector, but Equatable does because two vectors are equal if their components are all equal. Next you will implement Equatable.

To implement the protocol, add the following code at the end of the playground:

extension Vector: Equatable {
  static func= =(left: Vector, right: Vector) -> Bool {
    return left.x == right.x && left.y == right.y && left.z == right.z
  }
}
Copy the code

Add the following code to the bottom of PalyGround and test its output

vectorA == vectorB // false
Copy the code

The above code returns false because a vectorA and a vectorB have different components. Implementing the Equatable protocol does more than check the equality of these types. You also get free access to the vector array contains(_:)!

Create custom operators

Keep in mind that we generally discourage the use of custom operators. There are exceptions to this rule, of course.

A good rule of thumb for custom symbols is that they should only be used if:

  • Their meanings are well known or meaningful to the person reading the code.
  • They are easy to type on a keyboard. The last operator you want to implement meets both conditions. The vector dot product operates on two vectors and returns a single scalar number. Your operator multiplies each value in the vector by the corresponding value in the other vector, and then adds up all these products. The dot product has the sign theta, you can use the keyboardOption-8Easy to type. You might be thinking, “I could do the same thing for all the other operators in this tutorial, right?”

Unfortunately, you can’t do that yet. In other cases, you overload an existing operator. For a new custom operator, you need to create the operator first.

Directly at the bottom of the Vector implementation, but above the CustomStringConvertible extension, add the following declaration:

infix operator• :AdditionPrecedence
Copy the code

• is defined as an operator placed between two other values and has the same precedence as the addition operator +. Ignore priorities for now, because you’ll learn about them.

Now that this operator is registered, add its implementation at the end of the operator extension, immediately below the implementation of operator * :

static func(left: Vector, right: Vector) -> Int {
  return left.x * right.x + left.y * right.y + left.z * right.z
}
Copy the code

Add the following code to playground to test his output:

VectorA • vectorB/ / 15
Copy the code

So far, everything looks good…… Or is it? Try the following code at the bottom of the playground:

VectorA • vectorB + vectorA// Error!
Copy the code

• and + now have the same precedence, so the compiler parses the expression from left to right. The compiler interprets your code as:

(vectorA • vectorB) + vectorA
Copy the code

This expression boils down to Int + Vector, which you have not implemented and do not intend to implement. How do you solve this problem?

Precedence Groups

All operators in Swift belong to a priority group that describes the order in which the operators are evaluated. Remember the order of operations in elementary school math? That’s basically what you’re dealing with here. In the Swift standard library, the order of precedence is as follows:

Here are some comments about these operators, which you may not have seen before:

  1. Displacement operator<<and>>Used for binary computation.
  2. You use conversion operatorsisandasTo determine or change the type of the value.
  3. nilMerge operator,??Provide default values for optional types.
  4. If your custom operator does not specify a priority, it is automatically assignedDefaultPrecedence.
  5. Ternary operators,? :, similar to if-else statements.
  6. AssignmentPrecedenceDerived from=After all the operations. The compiler resolves types with left affinity, soV1 plus v2 plus v3 is equal to v1 plus v2 plus v3.

Operators are resolved in the order in which they appear in the table. Try rewriting the following code using parentheses:

v1 + v2 * v3 / v4 * v5 == v6 - v7 / v8
Copy the code

When you’re ready to check out the math, check out the solutions below.

(v1 + (((v2 * v3) / v4) * v5)) == (v6 - (v7 / v8))
Copy the code

In most cases, you need to add parentheses to make the code easier to read. Either way, it’s useful to understand the order in which the compiler evaluates operators.

Dot operator priority

Your newly defined dot-product does not fit into any of these categories. It must have less precedence than the plus operator (as mentioned earlier), but is it really appropriate to CastingPrecedence or RangeFormationPrecedence?

Instead, you create your own priority group for your dot product operators.

Replace the original declaration of the • operator with the following:

precedencegroup DotProductPrecedence {
  lowerThan: AdditionPrecedence
  associativity: left
}

infix operator• :DotProductPrecedence
Copy the code

Here, you create a new priority group and name it DotProductPrecedence. You put it lower than AdditionPrecedence because you want the plus precedence operator. You can also make it a left association, because you want to evaluate it from left to right, just like you do with addition and multiplication. This new priority component is then assigned to the • operator.

Note: In addition to lowerThan, you can specify higherThan in DotProductPrecedence. This becomes important if you have multiple custom priority groups in a single project. The code you wrote earlier returns the result you expected:

VectorA • vectorB + vectorA/ / 29
Copy the code

Congratulations 💐– you’ve mastered custom operators

What can you do next

You can read the complete code for this tutorial, which is given at the end. At this point, you know how to define the Swift operator as needed. In this tutorial, you focus on using operators in the field of mathematics. In practice, you’ll find more ways to use operators.

A good demonstration of the use of custom operators can be seen in the Active Swift framework. One example is <~ for binding, an important feature in reactive programming. Here is an example of this operator in use:

let (signal, _) = Signal<Int.Never>.pipe()
let property = MutableProperty(0)
property.producer.startWithValues {
  print("Property received \ [$0)")
}

property <~ signal
Copy the code

Cartography is another framework that makes heavy use of operator overloading. The AutoLayout tool overloads the equality and comparison operators to make NSLayoutConstraint creation easier:

constrain(view1, view2) { view1, view2 inview1.width == (view1.superview! .width -50) * 0.5
  view2.width   == view1.width - 50
  view1.height  == 40view2.height == view1.height view1.centerX == view1.superview! .centerX view2.centerX == view1.centerX view1.top >= view1.superview! .top +20
  view2.top == view1.bottom + 20
}
Copy the code

In addition, you can always refer to the official custom Operator Documentation.

With these new sources of inspiration, you can go out into the world and make your code simpler with operator overloading. Be careful not to get too enamored with custom operators! :]

extension Vector: ExpressibleByArrayLiteral {
  init(arrayLiteral: Int...). {assert(arrayLiteral.count= =3."Must initialize vector with 3 values.")
    self.x = arrayLiteral[0]
    self.y = arrayLiteral[1]
    self.z = arrayLiteral[2]
  }
}

precedencegroup DotProductPrecedence {
  lowerThan: AdditionPrecedence
  associativity: left
}

infix operator• :DotProductPrecedence

extension Vector: CustomStringConvertible {
  var description: String {
    return "(\(x).\(y).\(z))"}}let vectorA: Vector = [1.3.2]
let vectorB: Vector = [-2.5.1]

// MARK: - Operators
extension Vector {
  static func + (left: Vector, right: Vector) -> Vector {
    return [
      left.x + right.x,
      left.y + right.y,
      left.z + right.z
    ]
  }
  
  static prefix func - (vector: Vector) -> Vector {
    return [-vector.x, -vector.y, -vector.z]
  }
  
  static func - (left: Vector, right: Vector) -> Vector {
    return left + -right
  }
  
  static func * (left: Int, right: Vector) -> Vector {
    return [
      right.x * left.right.y * left.right.z * left]}static func * (left: Vector, right: Int) -> Vector {
    return right * left
  }
  
  static func * (left: Vector, right: Vector) -> Vector {
    return [
      left.y * right.z - left.z * right.y,
      left.z * right.x - left.x * right.z,
      left.x * right.y - left.y * right.x
    ]
  }
  
  static func(left: Vector, right: Vector) -> Int {
    return left.x * right.x + left.y * right.y + left.z * right.z
  }
}

vectorA + vectorB // (-1, 8, 3)
-vectorA / / (1, 3, 2)
vectorA - vectorB // (3, -2, 1)

extension Vector: Equatable {
  static func= =(left: Vector, right: Vector) -> Bool {
    return left.x == right.x && left.y == right.y && left.z == right.z
  }
}

vectorA == vectorB // falseVectorA • vectorB/ / 15VectorA • vectorB + vectorA/ / 29

Copy the code