قالب وردپرس درنا توس
Home / IOS Development / Overload of custom operators in Swift

Overload of custom operators in Swift



Update Note : Owen Brown updated this tutorial for Xcode 11, iOS 13 and Swift 5. Evan Dekhayser wrote the original.

Operators are the most important building blocks of any programming language. Can you imagine programming without using + or = ?

Operators are so basic that most languages ​​bake them as part of their compiler (or interpreter). The Swift compiler, on the other hand, does not code hard for most operators, but instead provides libraries a way to create their own. It leaves the work of Swift Standard Library to give you all the usual you expect. This difference is subtle, but opens the door to a huge potential for adaptation.

Fast operators are especially powerful because you can change them to suit your needs in two ways: assigning new functionality to existing operators (known as operator overload ) and creating new custom operators.

Throughout this tutorial, you use a simple Vector structure and build your own set of operators to help you compose different vectors together.

Getting Started [19659007] Open Xcode and create a new playground by going to File ▶ New ▶ Playground . Select the Blank template and name your playground CustomOperators . Delete all the default code so you can start with a blank slate.

Add the following code to your playground:

  struct Vector {
la x: Int
la y: Int
la 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 {
was description: String {
return "( (x),  (y),  (z))"
}
}

Here you define a new Vector type with three properties that match two protocols. CustomStringConvertible protocol and description calculated property allows you to print a friendly String representation of Vector .

At the bottom of your playground, add the following lines:

  let vectorA: Vector = [1, 3, 2]
la vector B = [-2, 5, 1] as vector

You have just created two Vector s with single Array s, and no initializers! How did it happen?

ExpressibleByArrayLiteral protocol provides a frictionless interface to initialize a Vector . The protocol requires an unavailable initializer with a variable parameter: init (arrayLiteral: Int ...) .

The variable parameter arrayLiteral allows you to pass an unlimited number of values ​​separated by commas. For example, you can create a Vector as Vector (arrayLiteral: 0) or Vector (arrayLiteral: 5, 4, 3) .

protocol takes convenience a step further and allows you to initialize with an array directly, as long as you define the type explicitly, which is what you did for vectorA and vectorB . [19659002] The only caveat to this approach is that you must accept matrices of any length. If you put this code in an app, remember that it will crash if you pass in a group of a length other than the exact tree. The statement at the top of the initializer will alert you in the console during development and internal testing if you ever try to initialize a Vector with less than or more than three values. [19659002] Vectors alone are fine, but it would be even better if you could do things with them. Just as you did in primary school, you will start the learning journey with addition .

Overload of the auxiliary operator

A simple example of an overload of operators is the auxiliary operator. If you use it with two numbers, the following happens:

  1 + 1 // 2 

But if you use the same additional operator with strings, it has a completely different behavior:

  "1" + "1" // "11" 

When + is used with two integers, it adds them arithmetically. But when used with two strings, it connects them.

To overload an operator, you must implement a function called the operator symbol.

Note : You can define the overload function as a member of a type, and that is what you want to do in this tutorial. When you do, it must be declared static so that it is available without an instance of the type that defines it.

Add the following piece of code to the end of your 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
    ]
}
}

This function takes two vectors as arguments and returns the sum as a new vector. To add vectors, you simply need to add their individual components.

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

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

You can see the resulting vector in the right sidebar of the playground.

Other types of operators

The additional operator is what is known as an infix operator, which means that it is used between two different values. There are other types of operators as well:

  • infix : Used between two values, such as the auxiliary operator (e.g. 1 + 1 )
  • prefix : Added before a value , such as the negation operator (e.g., -3 ).
  • postfix : Added after a value, such as the power decay operator (e.g. mayBeNil! )
  • ternary : Two symbols inserted between three values. Swift does not support user-defined ternary operators, and there is only one built-in ternary operator that you can read about in Apple's documentation.

The next operator you want to overload is the negation sign, which will change the character of each component in Vector . For example, if you apply it to vectorA which is (1, 3, 2) it returns (- 1, -3, -2) .

Add this code under the previous static function, by extension:

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

Operators are believed to be infection so if you want your operator to be a different type, you must specify the operator type in the feature statement. The negation operator is not infix so you add the prefix to the function declaration.

At the bottom of your playground, add the line:

  -vectorA // (-1, -3, -2)

Check if sidebar results are correct.

Next is subtraction, which I leave to you to implement yourself. When you're done, make sure your code is similar to mine. Tip: Subtraction is the same as adding a negative.

Give it a shot, and if you need help, check out the solution below!

[spoiler title=”Solution”]

  static funk - (left: vector, right: Vector) -> Vector {
go left - right
}

[/spoiler]

Test your new carrier by adding this code to the bottom of your playground:

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

Mixed parameters? No problem!

You can also multiply vectors by a number through scalar multiplication. To multiply a vector by two, multiply each component by two. You will implement this next.

One thing to consider is the order of the arguments. When you implemented additions, orders didn't mean because both parameters were vectors.

For scale multiplication, you must account for 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 funk * (left: Int, right: vector) -> Vector {
return [
    right.x * left,
    right.y * left,
    right.z * left
  ]
}

static funk * (left: Vector, right: Int) -> Vector {
back right * left
}

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

In mathematics, vectors have another interesting operation known as the cross product . How cross-products work is 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 will open the Emoji menu while you code?), It would be very convenient to reuse star for cross product operations.

Cross-products, unlike scale multiplication, take two vectors as arguments and return a new vector.

Add the following code to add the cross-production implementation 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
  ]
}

Now add the following calculation to the bottom of your playground and take advantage of both the multiplication and cross product operators:

  vectorA * 2 * vectorB // (-14, -10, 22)

This code finds the scalar multiple of vector A and 2, and then finds the cross product of that vector with vector B . Note that the star operator always goes from left to right, so the previous code is the same as if you had used parentheses to group the operations, such as (vector A * 2) * vector B .

Protocol Operators

Some operators are mandatory protocol members. For example, a type corresponding to Equatable must implement the operator == . Similarly, a type corresponding to Comparable must implement at least < and == because Comparable inherits from Corresponding . Similar types can also optionally implement > > = and <= but these operators have standard implementations.

For Vector Comparable doesn't make much sense, but Equatable does, since two vectors are equal if their components are all equal. You implement Equatable next.

To comply with the protocol, add the following code to the end of your playground:

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

Add the following line to the bottom of your playground to test this out:

  vectorA == vectorB // false

This line returns false as expected, because vectorA has components other than vectorB .

In accordance with Similarly, gives you more than the opportunity to check for similarity of these types. You also get access to contains (_ :) for a Array by Vector s for free!

Creating Custom Operators

Remember how I said that using custom symbols is generally not recommended? As always, there are exceptions to the rule.

A good rule of thumb about custom symbols is that you should only use them if the following is true:

  • Their meanings are well known or would make sense to anyone reading the code.
  • They are easy to type on the keyboard.

This last operator you implement matches both of these conditions. The vector dot product takes two vectors and returns a single scalar number. Your operator will multiply each value in a vector with its counterpart in the other vector, and then add up all these products.

The dot product symbol is which you can easily type with Option 8 on the keyboard.

You might be thinking, " I can only do the same thing I did with all other operators in this tutorial, right? "

Unfortunately, you can't do that yet. In the other cases, you overload an operator that already exists. For new custom operators, you must create the operator first.

Directly under the Vector implementation, but above the CustomStringConvertible compliance extension, add the following statement:

  infix operator •: AdditionPrecedence

This defines as an operator that must be placed between two other values ​​and has the same precedence as the auxiliary operator + . Just ignore priority at the moment because you want to get back to it.

Now that this operator is registered, you can add the implementation to the end of the operator extension, just below the implementation of the multiplication and cross product operators * :

  static funk • (left: vector, right: vector ) -> Int {
return left.x * right.x + left.y * right.y + left.z * right.z
}

Add the following code at the bottom of your playground to test this out:

  vectorA • vectorB // 15

Everything looks good so far … or does it? Try the following code at the bottom of the playground:

  vectorA • vectorB + vectorA // Error!

Xcode is not very happy with you. But why?

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

  (vector A • vector B) + vector A

This expression boils down to Int + Vector that you have not implemented and do not plan to implement. What can you do to fix this?

Precedence Groups

All operators in Swift belong to a priority group which describes the order in which the operators should be evaluated. Do you remember learning the order of operations in elementary school math? That's really what you're dealing with here.

In the Swift standard library, the order is as follows:

 Swift custom operator precedence and associativity table

Here are a few notes about these operators, since you may not have seen them before :

  1. Bit shift operators, << and >> are used for binary calculations.
  2. You use casting operators, are and like to determine or change a value type.
  3. Zero interconnection operator, ?? contributes to providing a setback value for optional values.
  4. If your custom operator does not specify a precedence, Standard Precedence is automatically assigned.
  5. The ternary operator, ? : is analogous to another statement.
  6. AssignmentPrecedence for derivatives of = is evaluated after everything else, no matter what.

The compiler analyzes types that have a left associativity such that v1 + v2 + v3 == (v1 + v2) + v3 . The opposite is the case for right associativity.

Operators are analyzed in the order they appear in the table. Attempt to rewrite the following code using parentheses:

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

When you're ready to check your math, take a look at the solution below.

[spoiler title=”Solution”]

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

[/spoiler]

In most cases, you want to add parentheses to make your code easier to read. However, it is useful to understand the order in which the compiler evaluates operators.

Dot Product Precedence

Your new dot product does not really fit into any of these categories. It must be less than add-ons (which you realized before), but does it really fit into CastingPrecedence or RangeFormationPrecedence ?

Instead, make your own priority group for your dot product operator.

Replace your original statement from operator with the following:

  DotProductPrecedence priority group {
lowerThan: AdditionPrecedence
associativity: left
}

infix operator •: DotProductPrecedence

Here you create a new priority group and name it DotProductPrecedence . You place it lower than AdditionPrecedence because you want additions to take precedence. You also make it left-associative because you want it evaluated from left to right as you do in addition and multiplication. Then assign this new priority group to your operator.

Note : In addition to lowerThan you can also specify higherThan in your DotProductPrecedence . This becomes important if you have multiple custom priority groups in a single project.

The old code line now runs and returns as expected:

  vectorA • vectorB + vectorA // 29

Congratulations – you've mastered custom operators!

Where to Go From Here?

You can download the finished version of the playground using the Download Materials button at the top or bottom of this guide.

At this point, you know how to bend Swift operators to your needs. In this tutorial you focused on using operators in a mathematical context. In practice, you will find many more ways to use operators.

A great demonstration of custom operator usage can be seen in the ReactiveSwift framework. An example is <~ for binding, an important function of reactive programming. Here is an example of this operator in use:

  let (signal, _) = Signal  .pipe ()
let property = MutableProperty (0)
property.producer.startWithValues ​​{
print ("Property Received  ($ 0)")
}

property <~ signal

Cartography is another framework that strongly uses operator overload. This AutoLayout tool overloads the equation and comparison operators to make NSLayoutConstraint easier:

  constrain (view1, view2) {view1, view2 in
view1.width == (view1.superview! .width - 50) * 0.5
view2.width == view1.width - 50
views1.height == 40
view2.height == view1.height
view1.centerX == view1.superview! .centerX
view2.centerX == view1.centerX

view1.top> = view1.superview! .top + 20
view2.top == view1.bottom + 20
}

In addition, you can always refer to the official custom operator documentation from Apple.

Armed with these new sources of inspiration, you can travel the world and make the code easier with operator overload. Just be careful not to go crazy crazy with custom operators! :]

If you have any questions or comments about this tutorial, please join the discussion below!


Source link