Operating on Data - Introducing Actions - Swift For Dummies (2015)

Swift For Dummies (2015)

Part II. Introducing Actions

In this part . . .

· Manipulate your data with actions.

· Categorize data with types.

· Organize data with collections.

· Manage the sequence of actions.

· Turn expressions into functions.

Chapter 5. Operating on Data

In This Chapter

arrow Using a playground to check syntax

arrow Getting a high-level view of arithmetic operators

arrow Exploring Swift's Boolean operators

Actions, the subject of this part of the book, are the heart of apps. At the heart of actions, are operators — the symbols that act directly on objects or values. Although actions are the heart of apps, you may or may not use them directly. When you’re using a framework such as Cocoa or Cocoa Touch, the classes in that framework perform many actions for you — it’s the framework’s code that invokes the actions that use the operators described in this part of the book.

Some apps live totally in the world of the frameworks with their generic operations to manage data and interface elements whereas others rely directly on their own actions to work with their custom data and interface elements. The specifics of your app — as well as your experience and programming style — determine which of these two approaches you’ll use.

Don’t worry: It’s quite possible to get far in the world of Cocoa and Cocoa Touch apps without touching the topics in this chapter. However, when you need them, they’re here. For now, browse over this chapter and the others in Part II so that you have an idea of what’s covered. Then, later, when you suddenly need to find out why a simple addition statement seems to have gone awry, for example, check out this chapter’s coverage of the addition operator. After all, it’s easy to get tripped up by the details of basic points like these.

icon tip Note that most operators are special character symbols alone or in pairs such as + or ! or ||, but some are text, such as AND. In addition, some operators have both symbol and text versions that mean the same thing. And, to keep you on your toes, some symbols are paired with text operators for which the symbol and the text operators are similar but have subtle differences. For instance, as in C, AND is a synonym for && — a logical AND that operates on a Boolean value of true or false; & is a bitwise AND that operates on the bits of an entire word.

If you are familiar with other programming languages (and you should be familiar in a general way with at least one), most of these operators won’t be totally new to you. However, Swift requires a few tweaks to some of them, so don’t skip this chapter, even if you only read it quickly.

Classifying Operators

Whether symbols or text, operators act on operands, which are values — either specific values (such as 17.5) or expressions that yield values (such as 16.5 + 1). Combining an operator with the appropriate number of operands yields a value which can then be used itself as an operand in another operation.

Operands are sometimes referred to as the targets of operators. Whether you refer to them as operands, targets, or the more generic values, they may be single values such as 17.5 or the result of expressions such as 16.5 + 1. See Chapter 10, “Expressing Yourself” for more information on types.

Operators are classified by the number of operands on which they act, as follows:

· Unary: Unary operators operate on one target. (The term target is often used to describe a unary operand.) In Swift they appear immediately before or after the operand. However, these are not interchangeable — for example, a minus sign must immediately precede a number and cannot follow a number. When a unary operator precedes its target, it’s called a prefix operator; when it follows its target, it’s called a postfix operator.

· Binary: Binary operators operate on two targets. The basic arithmetic operators (+, -, /, and x) are binary operators. Whereas unary operators can be prefix or postfix operators, binary operators are infix operators because the operands (or targets) are placed on either side of the binary operator.

· Ternary: Ternary operators operate on three targets. The classic ternary operator in C is also implemented in Swift. It describes a conditional operator using syntax such as the one shown here which evaluates to either the second or third value depending on the test in the condition:

(targetValue > 5) ? valueForLessThan5 : otherValue

In general, an operator is classified as one of these three. These concepts are present in many computer languages including most of those derived from C.

Answering Syntax Questions with Playgrounds

As with many other languages, the operators in Swift were derived from common operators in other programming languages; Swift’s operators include some tweaks and modifications that make them different from the originals, and they include some new operators as well. If you have experience with several languages, it’s hard to keep straight all the details of these operators. It’s easy to lose track of which tweak belongs to which language. Furthermore, you may not even be aware of these differences: with the as-you-type correction offered in today’s powerful editing tools, you may actually type illegal syntax but never see it because Xcode or another tool automatically fixes the error.

Still, it’s easy to get stuck over a minor point of syntax. You can look it up (maybe you’ve bookmarked this page), or you can wait to see if the compiler objects. Playgrounds in Xcode 6 and later are perfect for answering these questions. (Chapter 2, “Playing in the Playground” has more information on playgrounds.)

Consider the simple question of whether a space can be placed between a minus sign and the value it negates. (This is one of the syntax issues that can arise when you decide to standardize indentations to make your code easier to read.) In fact, if you read Apple’s documentation carefully, you see that unary operators “appear immediately before or after an operand.” That immediately means no space, but a detail like that is easy to miss or forget.

Using a playground is the easiest way to answer a question like this. To do so, just follow these steps:

1. Create a playground as described in Chapter 2. Start from a basic playground such as the one shown in Figure 5-1.

Note that this example uses OS X and Swift in setting up the playground. You can tell because Cocoa is imported rather than UIKit. You can use either OS X or iOS for the examples in this chapter.

2. Enter some test code as shown in Figure 5-2. (For this example, make sure your test code includes a negative number.)

3. Change the syntax for a second test, as shown in Figure 5-3. (For this example, add a space between the negative sign and the number.)

The sequence doesn’t matter (you can test with or without the space in either step). The point is to answer the question: “Does the space matter?” It’s not hard to imagine a language in which the space is optional (but would you really want to write the language parser for that case?).


Figure 5-1: Creating a playground.


Figure 5-2: Entering your test syntax.


Figure 5-3: Testing a variation.

icon tip Leaving a playground window open all the time just for tests like this is a good idea. For many people, typing the test syntax to get an immediate answer to a syntax question is actually faster than looking up the class or language reference.

Clearing the Way for Operators

The operators described in this chapter operate on operands — variables, constants, or expressions. Many of the operators are arithmetic, but, as in other languages, logical (Boolean) operators are also implemented in Swift. Bitwise operators operate on the bits of a word just as they do in other languages.

Bitwise operators are used most frequently on integers and particularly on unsigned integers. In the case of unsigned integers, the bits of a word are evaluated as a binary value (such as 0111 being evaluated as 7). As in other languages and operating systems, at the hardware level a Boolean value is true (1) or false (0), and that value is stored in the low-order (0-th) bit of a word. This “wastes” the other bits. Bitwise operations can use all of the bits of a word so that bitwise operations may be significantly faster and take much less memory than operations that use the entire word for storing a Boolean value. The trade-off may come in complexity. This is not a Swift issue, and you can find many discussions of it in textbooks and articles on the web. You should know that the performance advantages of using bitwise operators have been rendered much less important than they were in the past with modern computers.

Whether you’re working with numbers, characters, or bits, Swift has one very important design feature that can make your life easier and your code more robust. All variables must be initialized before being used in Swift.

This restriction doesn’t apply to many other languages. You can declare a variable and then use it without initialization as in this pseudo-code:

integer i;
i = i + 3;

What is the value of i after these two lines are executed? There’s no way of telling because the initial value of i is unknown. In some cases, variables are automatically initialized (sometimes to a safe value such as 0 or an empty string in the case of a string). In many other cases, using an uninitialized variable can cause a crash or an undefined result.

In a similar way, pointers can be declared in many languages, but they need not have been pointed to anything in particular before they are used. Preventing the use of uninitialized variables and constants in Swift has been a major objective of the language and its implementation.

Remember this as you read through this section and think about the ways of preventing and recovering from undefined operations. When you don’t have to worry about uninitialized variables and constants, you can cross a number of concerns off your to-do list.

Assigning Values with Assignment Operators

Although Swift builds on both C and Objective-C, along with other languages, there are several ways in which it strikes out on a different course. One of these is in the interpretation of the assignment operator (=).

The assignment operator takes two operands: It sets the value of the one on the left to the value of the one on the right, as in

a = b


area = width * depth

The assignment operator must not be confused with the Boolean comparison operator, which is ==. In some languages, the assignment operator is actually valid in a Boolean comparison, and there are historic reasons for this (possibly related to the limited number of symbols on keypunch machines). For example, the following code is legal in C, Objective-C, and Swift:

if (width > depth) {. . .

In C and Objective-C, however, the Boolean > comparison operator can be replaced as follows:

if (width = depth) {. . .

The phrase width > depth appears to be similar to phrases such as width < depth and width = depth. All are valid syntax, but tread carefully here.

The comparison operator here (>) has been replaced with an assignment operator (=) rather than a comparison operator that tests for equality. Most of the time, the intent of this code is not assignment or replacement but comparison. To be a comparison, the code should be written as follows:

if (width == depth) {. . .

There really is no ambiguity here, but it is the rare developer who has never been caught confusing == and =. Swift addresses this issue by making = illegal in this case. Using == is not optional: it is required in Swift for testing equality in this way.

For the cases in which both assignment and a Boolean test are required, you must use two totally unambiguous statements (one for the assignment and one for the test).

Counting On Arithmetic Operators for Math

The earliest computers in the twentieth century were created to perform arithmetic operations. Indeed, modern computers are often traced to a paper written by Alan Turing in 1936 — On Computable Numbers. These days, with music and movies on mobile devices, we’ve moved far beyond computable numbers, but numbers and arithmetic remain at the core of computers. In Swift, the basic arithmetic operators are supported, although, as with other operators, some have been modified, refined, or enhanced.


The simple addition operator (+) is available in Swift. It acts on numbers but also can act on strings and characters for concatenation. It also has an additional feature which is shared with the other arithmetic operators that lets you handle over-and underflow conditions (This is discussed in the following section.)

Figure 5-4 shows the addition operator being used for concatenating two strings and a character (the blank space). You’ll notice that this string manipulation is different from the techniques in both C and Objective-C. (For one thing, it’s simpler than the C strcat function and thestringByAppendingString: message in Objective-C.)


Figure 5-4: Concatenating strings and characters.

Handling over- and underflow conditions

Swift provides an extension to basic arithmetic operators to handle the potential over- and underflow conditions that can occur. If you precede an arithmetic operator with &, the new operator handles over- and underflow conditions, as well as certain errors. (The new operator — &+, &-, &*,&/, or &% — is called an overflow operator.) If the addition operation, for example, would generate an invalid result (such as a value that cannot fit in the declared type), using & + instead of + will handle the operation without generating an error.

The most common example of this behavior is demonstrated by setting a variable to the maximum value of an integer and adding 1 to it. Normally, that produces an overflow, but if you use the & + operator, the result is 0.

Here is the code to ignore the overflow:

var testOverflow = UInt16.max
testOverflow = testOverflow &+ 1

The first line sets testOverflow to the maximum value of an unsigned 16-bit integer. (Working with an unsigned integer is the simplest case because you are dealing only with the binary representation of a number without regard to a sign.) Adding 1 to the maximum value for the type with & + results in 0 because the value has overflowed the size of the declared type. (If there were space, the value would be 0 with a leading 1 in the next digit, but there isn't space in the UInt16 type for that next digit.)

The overflow operator is defined differently for each arithmetic operation. For example, the result of the following line of code is 0 rather than an error. (The syntax error for the code without using the overflow operator may be generated as you type; if it is a result of division attempted with a variable the value of which is unknown until runtime, the error will be flagged at that point.)

var testDivision = 5 &/ 0

This method of working around a divide-by-zero error is very useful in many cases: You can avoid testing for zero and then forcing the value of zero to the variable that would have been the result of the division operation.

In some cases, the error and accompanying program stop or exception is what you want, but in other cases it’s not. The point is, Swift lets you choose. Over- and underflows along with illegal arithmetic operations such as division by zero have been issues from the earliest days of computers. In COBOL, for example, numbers are formatted using a PICTURE structure. A PICTURE functions much like an odometer in a car: adding 1 to 99999 rolls the value over to 00000. That is the behavior of &+, whereas the behavior of + in the same situation is to generate an error.

icon tip In the early 1950s this topic was the subject of much debate between the business-oriented programmers working on COBOL and the mathematicians working on FORTRAN. To this day, software that handles numbers that may cause over- or underflows often has code to handle the question of what adding 1 to a number like 99999 should do in the case where the result cannot be stored and/or printed.


The subtraction operator (–), also has a & variant. Remember that addition and subtraction turn into the same operation when the values involved are negative numbers.

icon tip In cases where you use & with an operator, it must precede the operator directly without an intervening space. The two-character combination is an overflow operator that is handled as a single entity when your code is parsed.


Multiplication, too, can use the overflow operator &. Figure 5-5 shows a variable set to the maximum Int16 value of 32,767 (note the Int16.max syntax). Listing 5-1 shows manipulations of that value that are also shown in Figure 5-5.


Figure 5-5: Manipulating a value with &*.

Listing 5-1: Manipulating a value at the edge of precision

var test = Int16.max
var test2 = test-1000
var test3 = test&*2 // result is -2002

As the playground shown in Figure 5-5 demonstrates, you can subtract 1,000 from that value without any problem. Multiplying that value using &* 2, however, gives the bitwise correct value of –2002, as shown in Figure 5-5, but that type of code is generally considered unadvisable. First of all, it’s hardware-dependent. Second of all, it’s not clear what you are doing, and anyone modifying the code later on may either misunderstand it or misinterpret your intentions. Perhaps worst of all, someone may “discover” a “bug” in your code and “correct” it to code that doesn’t work properly. From a maintenance standpoint, this is a time bomb.

icon tip Do you see how we got to –2002? It’s because the result of 31,767 multiplied by 2 overflows the Int16 type field. If there were space to add digits to the left, the result would be arithmetically correct, but because those high-order bits don’t exist, the number is incomplete — and wildly wrong.


Division is an operator that can pose problems in any computer language. The basic operator is /, but beyond that a variety of issues arise in any language — including Swift.

Although the topic of handling runtime errors arises here in the context of division, it arises in many other contexts with Swift, so this discussion is generalized. The handling of runtime errors, including handling operations that either cannot be performed or whose results are undefined is something that you need to handle in your apps. You may have dealt with it in other languages (particularly in C or Objective-C, which have the greatest influences on Swift and Cocoa or Cocoa Touch programming). The measures you take to avoid runtime errors generally work in Swift, but you have a wider variety of tools to avoid such errors, and as a result, you can push some of your error-avoidance code into Swift itself using tools such as optionals (see Chapter 6), and overflow operators, as discussed in this chapter.

Handling undefined results and errors

Perhaps the most common issue with division is the divide-by-zero error. The problem arises because dividing a value by zero is undefined. (If you want to investigate some of the history of the topic, look up George Berkeley and his 1734 book, The Analyst.) Computers and programming languages don’t generally have the ability to deal with uncertainties or, to use a mathematical phrase, indeterminate forms. This is not just a piece of history: Problems with undefined results occur frequently in both hardware and software (particularly programming languages and compilers).

icon tip This section outlines the major approaches to handling operations that may be undefined. These are general approaches that apply to most programming languages (they’re more a matter of system design than of specific languages). At the end of this section, you’ll see a very important feature of Swift that can significantly reduce the frequency with which you encounter undefined results. Uninitialized variables are banished from Swift thereby eliminating quite a few common problems in many other languages. (This is discussed in more detail at the end of this section.)

The basic approaches to handling undefined results are as follows:

· Ignoring undefined results: One way of handling undefined results is to simply ignore them. Believe it or not, there have been implementations where operators or functions encountering undefined results return a random value (or a random memory location interpreted as a numerical value). The argument for this is that “undefined means undefined,” so any value will suit the bill. Fortunately, this way of thinking has pretty much gone out of style.

· Returning an error: Another way to deal with undefined operations is to refuse to do them — to cause an error. The error can be anything from a crash of the app (or even the device!) to an error value or message. This technique involves returning two results from the operation — a value result and a status result. Many people believe that this is the most robust coding style because it separates the calculation from the status of the result. Unfortunately, it also adds more code to your app. However, incorporating calculations returning a value and a status into a reusable function or method can minimize any inconvenience.

This is one of the techniques that Swift uses — in fact, it’s the default behavior for the / operator.

· Returning a defined value: Logically, you need to return two results from an operation that can fail or be undefined. One classic way of doing this is to perform the operation and test the result (or test the operands before a result is produced). If the test produces an error or undefined result, you can return a known value that indicates an error.

In some programming languages (and on some computers) a value called a NAN (not a number) is returned. Its internal representation can vary, but when printed out, the value can be shown as NAN. Typically, a NAN cannot be used in further computations. If an error result is returned, it may indicate that the value returned (that is, the NAN) is unusable.

Alternatively, a returned value can be safe to use in further calculations without causing problems even though it is not a valid arithmetic result. In some divide-by-zero situations, returning 1 or the value of the numerator avoids returning an error while making it possible for further calculations to proceed.

This is another technique that Swift uses. If you divide by zero using the &/ operator, Swift returns 0, as you can see in line 1 of Figure 5-6. A simple / operator causes a division by zero error.

technicalstuff The trade-off in all cases of calculations that are undefined is the balance between letting the program continue even with possibly misleading calculations (0 or 1 to bypass a possible divide-by-zero error) or letting it take an error branch and unambiguously stop its processing.

· Preventing errors: The best way to handle an operation that could fail or be undefined is to catch the situation before it happens. Instead of relying on a status result, you avoid even attempting an operation that results in a problem. By moving that evaluation out of the operation itself, you may make your code more maintainable. In most cases, preventing the error means modifying an operand or substituting a “result” that is appropriate as a default for your case.

· Avoiding errors: The final strategy is to avoid the possibility of an error. Instead of manipulating the operands or the result, you just don’t do anything that could cause the problem. This may be as simple as displaying a message that the area of a polygon can’t be calculated in certain circumstances. When a user is, say, selecting a movie to play and encounters a divide-by-zero error, it’s usually preferable, particularly in Cocoa Touch apps, to do something appropriate rather than to ask the user to help solve the problem.

Remember that Swift goes out of its way to prevent you from having uninitialized variables and constants. There's no guarantee that they will be initialized to correct values, but the fact that you don't have to worry about totally uninitialized variables can make your life as a developer easier than otherwise.


Figure 5-6: Managing division errors with &/.

Using remainder division

Even if both the numerator and denominator are integers, the result of division may be a non-integer value. This sets division apart from the other arithmetic operators, whose operations involving integers (any number of them) always return an integer.

It is frequently the case that you need to handle division of integers and use the result as one or more integers. Perhaps the most common case is when you need to paginate some data: Let’s say you have 17 items to list and you can include 5 items per page. How many pages will you need? There are two ways to handle this in Cocoa and Cocoa Touch:

· Use NSTableView in Cocoa or UITableView in Cocoa Touch and don’t give it another thought.

· Calculate the answer yourself using integer division and the remainder operator. (There are other ways of doing this, but this section focuses on remainder division so that is the approach that is used here.)

The code in Listing 5-2 defaults the variables to integers, as you can see in Figure 5-7. (You can tell this because in the sidebar they are shown without a decimal point.)


Figure 5-7: Managing remainder division with integers.

Listing 5-2: Using Default Integers for Remainder Operations

var itemCount = 17
var itemsPerPage = 5

var pageCount = itemCount/itemsPerPage //3
var leftOvers = itemCount%itemsPerPage //2

var totalPages = pageCount + (leftOvers > 0 ? 1 : 0) // 4

This is basic C code, and it works in Swift. What’s important to note is that Swift is inferring the types involved (there’s more on this in Chapter 6). The ternary expression in the last line tests to see if leftOvers is not equal to 0, and, if so, it adds a single page to the page count to accommodate the leftovers.

In Listing 5-3 (and in Figure 5-8), you can see how things work with the % (remainder) operator when the variables are floats (using the Float type).


Figure 5-8: Managing remainder division with floats.

In Figure 5-8 and Listing 5-3, the variables are declared as Float, and when they are used in a calculation (see the calculation of leftOvers) the result is a floating point number instead of an integer.

Listing 5-3 not only uses floating point numbers, but it also uses the ceiling function (ceil) that rounds up the result of a division operation. That is exactly what is needed here. In fact, if the ternanary operation remains as the last line of code, it will produce an incorrect result fortotalPages because ceil has addressed the issue directly.

You don't need to calculate leftOvers as shown in Figure 5-3: It is sufficient to use ceil to calculate pageCount. If you need to know how many items will appear on the final partial page, you do need leftOvers, but in many cases you will just use a loop to fill the pages. This reduces the five lines of code in Listing 5-2 to three lines of code. When you reduce the number of lines of code you have to write to accomplish a task, you’re almost always on the right track.

Listing 5-3: Using Floats for Remainder Operations

var itemCount: Float = 17
var itemsPerPage: Float = 5

var pageCount: Float =
ceil (Float (itemCount/itemsPerPage)) //4.0
var leftOvers: = itemCount%itemsPerPage //2.0

Incrementing and decrementing numeric values

Although a few features from C and Objective-C aren’t implemented in Swift, most of the features from those languages are implemented, the majority of which are implemented without changes. Among the unchanged features are the increment and decrement operators as well as the combined operators.

Increment and decrement operators add or subtract 1 from a value. They are most frequently used in loops, but you can use them anywhere. One form of the syntax is

i ++


i --

With this syntax, the value of i is returned and, after that, is incremented or decremented. You can also reverse the order, like this:

++ i


-- i

By reversing this order, you increment or decrement the value of i first and then return the incremented or decremented value.

Combining operators

With this pattern in mind, you can combine other operators and operands. The increment/decrement operators function along the lines of combined operators. To use a combined version of an increment operator, for example, you could write:

var a = 2
a += 1

The value of a at the end of this snippet is 3 (1 is added to 2 and a is set to the result).

The combined operator + = provides more flexibility than the simple ++ operator and its variations (such as the decrement operator and all prefix and postfix positions). You can vary the increment value from 1 and even change the operator so that you get something like this:

var a = 2.0
a *= 21.6

As I discuss in Chapter 6, Swift’s type safety requires you to make certain that you’re using appropriate types. Swift can infer types, but it doesn’t automatically convert types. Multiplication is not defined for two dissimilar types (an integer and a floating point number, for example). This is why you have to declare a with an initial value of a Float to prepare for the next line of code. Alternatively, you could rewrite the second line of code as a * = Int(21.6) to solve the problem on the other side.

You’re not limited to addition or subtraction and you’re not limited to a value of 1 as you can see in Figure 5-9.


Figure 5-9: Combining operators.

Comparing values

The standard C comparison operators shown in Table 5-1 are supported in Swift.

Table 5-1 Comparison operators



Equal value


Identity object


Greater than


Less than


Greater than or equal


Less than or equal


Any of the operators can be preceded by a ! (not) operator.

The identity operator may be new to you. It handles a problem, common in object-oriented programming, that occurs when comparing two instances. This issue occurs frequently when you’re comparing two NSString instances, although it arises in other cases as well.

Here’s an example. Assume you have two instances of NSString, one containing Hello and the other containing World. They are not equal and they are not identical. Now change the value of the second string to Hello, so that both NSString instances contain the same letters. As you can see in Figure 5-10, the values of these two instances are now equal (Hello), but their identities are not because they are two separate instances. This is where the identity operator comes in handy. When you want to compare values, use two equal signs (==); when you want to see if two objects are the same instance, use three equal signs (===).


Figure 5-10: Testing for equality of value or of instance.

Choosing and Checking Values with Logical Operators

Logical operators operate on Boolean values, which can either be true or false. Depending on your background, this may or may not be a gotcha waiting to strike. A Boolean value is true or false, and it’s represented by one bit in a computer word. The remaining bits in that word are irrelevant to the evaluation of the Boolean value.

The actual values representing true and false differ from one language to another: in Swift, the values are true and false. In Objective-C, the basic values are YES and NO. In other languages, the implementations of Boolean variables are 0 and 1, with the underlying type being an integer.

Rather than wasting the remaining bits in a word, there are bitwise operators that compare the bits in two words, thereby potentially using many (perhaps as many as 64) Boolean values within a single word.

In Swift, each of the logical operators has a graphical representation based on one or two characters. The operators and those representations are as follows:

· And &&: This operator evaluates to a true value if both operands themselves are true. Put another way, if any operand is false, the result is false. Because of this, as soon as the operating system encounters a false operand, the result of the operation is set to false.

This shortcut that triggers as soon as a false value is encountered in an AND (or, conversely a true value is encountered in an OR) is a common optimization in many programming languages and their implementations, but it means that if an operand is an expression that sets a value as part of its operation, that expression will not be executed in all cases because it may be unnecessary when a shortcut has been encountered. This means that the order of these expressions matters in your code. If there's a byproduct of a Boolean expression that sets a value and a shortcut is taken before that expression is evaluated, you may have created a bug that's difficult to find.

· Or ||: With this operator, if any operand is true, the expression will evaluate to true. The same rule about sequence applies.

· Not !: This is a unary operator that inverts the Boolean value of a variable or expression.

You can combine Boolean operations into a complex expression. As is the case with arithmetic expressions, you can use parentheses to control the order of execution of the various parts of the full expression. Parentheses, together with indentation and line spacing in your source code can make your intention clear. Furthermore, consider the people who will maintain your code in the future and think about splitting overly complex expressions into several parts that may be easier to read and maintain.