Patterns and pattern matching

In the previous section, we looked at simple pattern matching examples for enumerations. In this section, we will examine patterns and pattern matching in detail.

The wildcard pattern

The wildcard pattern matches and ignores any value. It consists of an underscore, _. We use a wildcard pattern when we do not care about the values being matched against.

For instance, the following code example ignores the matched values:

for _ in 1...5 {
    print("The value in range is ignored")
}

We use _ to ignore the value in the iteration.

The wildcard pattern can be used with optionals as follows:

let anOptionalString: String? = nil

switch anOptionalString {
    case _?: print ("Some")
    case nil: print ("None")
}

As seen from the preceding example, we matched an optional by _?.

The wildcard pattern can be used to ignore data that we do not need and values that we do not want to match against. The following code example presents the way in which we use the wildcard pattern to ignore the data:

let twoNumbers = (3.14, 1.618)

switch twoNumbers {
    case (_, let phi): print("pi: (phi)")
}

The value-binding pattern

A value-binding pattern binds matched values to variable or constant names. The following example presents the value binding pattern by binding x to 5 and y to 7:

let position = (5, 7)

switch position {
    case let (x, y):
        print("x:(x), y:(y)")
}

The identifier pattern

An identifier pattern matches any value and binds the matched value to a variable or constant name. For instance, in the following example, ourConstant is an identifier pattern that matches the value of 7:

let ourConstant = 7

switch ourConstant {
    case 7: print("7")
    default: print("a value")
}

The identifier pattern is a subpattern of the value-binding pattern.

The tuple pattern

A tuple pattern is a comma-separated list of zero or more patterns, enclosed in parentheses. Tuple patterns match values of corresponding tuple types.

We can constrain a tuple pattern to match certain kinds of tuple types using type annotations. For instance, the tuple pattern (x, y): (Double, Double) in the declaration, let (x, y): (Double, Double) = (3, 7), matches only tuple types in which both elements are of the Double type.

In the following example, we match the pattern by binding the name, checking whether age has a value, and finally, if the address is of the String type. We use only the name that we need and, for age and address, we use the wildcard pattern to ignore the values:

let name = "John"
let age: Int? = 27
let address: String? = "New York, New York, US"

switch (name, age, address) {
    case (let name, _?, _ as String):
        print(name)
    default: ()
}

The enumeration case pattern

An enumeration case pattern matches a case of an existing enumeration type. Enumeration case patterns appear in a switch statement's case labels and case conditions of if, while, guard, and for-in statements.

If the enumeration case that we are trying to match has any associated values, the corresponding enumeration case pattern must specify a tuple pattern that contains one element for each associated value. The following example presents the enumeration case pattern:

let dimension = Dimension.metric(9.0, 6.0)

func convertDimensions(dimension: Dimension) -> Dimension {
    switch dimension {
    case let .us(length, width):
        return .metric(length * 0.304, width * 0.304)
    case let .metric(length, width):
        return .us(length * 3.280, width * 3.280)
    }
}

print(convertDimensions(dimension: dimension))

In the preceding example, we use tuple pattern for each associated value (length and width).

The optional pattern

An optional pattern matches values wrapped in a Some(Wrapped) case of an Optional<Wrapped> or ImplicitlyUnwrappedOptional<Wrapped> enumeration. Optional patterns consist of an identifier pattern followed immediately by a question mark and appear in the same places as enumeration case patterns. The following example presents optional pattern matching:

let anOptionalString: String? = nil

switch anOptionalString {
    case let something?: print("(something)")
    case nil: print ("None")
}

Type casting patterns

There are two type casting patterns as follows:

  • is: This matches the type against the right-hand side of the expression
  • as: This casts the type to the left-hand side of the expression

The following example presents the is and as type casting patterns:

let anyValue: Any = 7

switch anyValue {
    case is Int: print(anyValue + 3)
    case let ourValue as Int: print(ourValue + 3)
    default: ()
}

The anyValue variable is type of Any storing, an Int value, then the first case is going to be matched but the compiler will complain, as shown in the following image:

Type casting patterns

We could cast anyValue to Int with as! to resolve the issue.

The first case is already matched. The second case will not be reached. Suppose that we had a non-matching case as the first case, as shown in the following example:

let anyValue: Any = 7 
 
switch anyValue { 
     case is Double: print(anyValue) 
     case let ourValue as Int: print(ourValue + 3) 
     default: () 
} 

In this scenario, the second case would be matched and cast anyValue to Int and bind it to ourValue, then we will be able to use ourValue in our statement.

The expression pattern

An expression pattern represents the value of an expression. Expression patterns appear only in a switch statement's case labels. The expression represented by the expression pattern is compared with the value of an input expression using the ~= operator.

The matching succeeds if the ~= operator returns true. By default, the ~= operator compares two values of the same type using the == operator. The following example presents an example of the expression pattern:

let position = (3, 5)

switch position {
    case (0, 0):
        print("(0, 0) is at the origin.")
    case (-4...4, -6...6):
        print("((position.0), (position.1)) is near the origin.")
    default:
        print("The position is:((position.0), (position.1)).")
}

We can overload the ~= operator to provide custom expression matching behavior.

For instance, we can rewrite the preceding example to compare the position expression with a String representation of positions:

func ~=(pattern: String, value: Int) -> Bool {
    return pattern == "(value)"
}

switch position {
    case ("0", "0"):
        print("(0, 0) is at the origin.")
    default:
        print("The position is: ((position.0), (position.1)).")
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset