Skip to content

[Swift 6]: Update optional and exercise #830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ if
let conceptExerciseTargets: [Target] = conceptExercises.flatMap {
return [
.target(
name:"\($0.pascalCased)",
name:"\($0.pascalCased)",
dependencies: [.product(name: "Numerics", package: "swift-numerics")],
path:"./exercises/concept/\($0)/.meta/Sources"),
.testTarget(
name:"\($0.pascalCased)Tests",
Expand Down
4 changes: 3 additions & 1 deletion concepts/optionals/.meta/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
"authors": [
"wneumann"
],
"contributors": []
"contributors": [
"meatball133"
]
}
111 changes: 56 additions & 55 deletions concepts/optionals/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

## Optionals

Swift uses _optionals_ to allow programmers to represent the possible absence of a value. Before attempting to use a value that may not exist, optionals allow the program to check first it it exists, then if it does exist unwrap and use it.
Swift uses [_optionals_][optionals] to allow programmers to represent the possible absence of a value.
Optional is a type that can either hold a value or be [`nil`][nil], which represents the absence of a value.
Using an optional requires a program to check if a value does exist before using it after unwrapping it.

Any type can be made into an optional by appending a `?` onto the end of the type name. So an optional integer would have type `Int?` and an optional string would have type `String?`. Defining constants or variables of optional type and assigning them values is done the same as for values of non-optional types.
Any type can be made into an optional by appending a `?` onto the end of the type name.
So an optional integer would have type `Int?` and an optional string would have type `String?`.
Defining constants or variables of optional type and assigning them values is done the same as for values of non-optional types.

```swift
let x: Int? = 42
var y: String? = "Hello"
y = "Goodbye"
```

You can assign the absence of a value to a variable of optional type by assigning it the special value `nil`. `nil` can be used with all optional types, but `nil`s assigned to two different optional types do not have the same type, and cannot be interchanged or even compared. E.g.
You can assign the absence of a value to a variable of optional type by assigning it the special value `nil`.
`nil` can be used with all optional types, but `nil`s assigned to two different optional types do not have the same type, and cannot be interchanged or even compared.

```swift
let intOpt: Int? = nil
Expand All @@ -21,56 +26,63 @@ let stringOpt: String? = nil
intOpt = stringOpt
// Compiler error: Cannot assign value of type 'String?' to type 'Int?'

intopt == stringOpt
intOpt == stringOpt
// Compiler error: Binary operator '==' cannot be applied to operands of type 'Int?' and 'String?'
```

Note that when declaring a variable or constant and assigning `nil` to it, a type annotation is required. This is because without the annotation, the compiler cannot determine which optional type to infer. Also, if a variable is defined with an optional type annotation, but neither a value nor `nil` is assigned to it, the variable will be automatically populated with a nil.
Also note that even though `nil` can be assigned to any optional type, it cannot be assigned to a non-optional type (even if it doesn't actually hold `nil`).
And methods that is expecting a non-optional type cannot be passed an optional type without unwrapping it first.

```swift
var a: Int?
a ?? 0 // evaluates to 0

var b = nil // Compiler error: 'nil' requires a contextual type
var x: Int = 42
var y: Int? = 42

y = x
// Works fine
x = y
// error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'
x + y
// error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'
```

An example of where optionals arise in Swift is in the initialization of `Int`s and `Double`s from strings. For example, you can convert the string `"123"` to an integer by writing `let newInt = Int("123")`. However, if you do this you will find that the type of `newInt` is not `Int`, but rather `Int?`. This is because not all strings can sensibly be converted to `Int`s. What should the result of `Int("123.45")` or `Int("horse")` be. In cases like this, where there is no sensible value to return, the conversion returns `nil`, and so the return type must be `Int?`.

You can read more about optionals at [A Tour of Swift: Optionals][optionals].

## Using optionals

Because optional types are not the same types as their base types, the two types cannot be used in the same ways. For example:
`Int("123") + 1` results in a compiler error "Value of optional type 'Int?' must be unwrapped to a value of type 'Int'". In order to access the `Int` from the conversion, one must "unwrap" it first. This can be done with the force-unwrap operator, `!`. Appending this operator to an optional value will return the base value within. However, force-unwrapping a `nil` will result in a runtime error that will crash the program.
Because optional types are not the same types as their base types, the two types cannot be used in the same ways.
For example: `Int("123") + 1` results in a compiler error "Value of optional type 'Int?' must be unwrapped to a value of type 'Int'".
This is because the `Int("123")` returns an optional `Int?` type, not an `Int` type, since if the string cannot be converted to an integer, the result will be `nil`.
In order to access the `Int` from the conversion, one must "unwrap" it first.

This is most commonly done in Swift using the `if-let` and `guard-let` constructs for [_optional binding_][optional-binding] which check for `nil` and take one code path with the unwrapped value bound to a supplied name if a value exists and taking a different code path if `nil` was found.

```swift
Int("123")! + 1 // evaluates to 124
Int("123.45")! + 1 // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
if let safeNum = Int("123") {
let sum = safeNum + 1
} else {
// code for the case where nil was found -- may be left out
}
```

As force unwrapping a `nil` crashes a program the use of the force-unwrap operator is _strongly_ discouraged in Swift. One can make the use of the operator safer by explicitly checking for `nil` before unwrapping:
It is worth noting that the `safeNum` variable has the type `Int` and not `Int?`.
In the example below, `num` is of type `Int?` and `safeNum` is of type `Int`.

```swift
let num = Int("123")
let sum : Int
if num != nil {
sum = num! + 1
if let safeNum = num {
// num is of type Int
}
```

While is is safer, it leads to cluttered programs, so Swift also offers the `if-let` and `guard-let` constructs for _optional binding_ which check for `nil` and take one code path with the unwrapped value bound to a supplied name if a value exists and taking a different code path if `nil` was found.
This _optional binding_ is important because it unwraps (or "removes") the optional type from the value, allowing it to be used as a non-optional value.
If you would just do a conditional check on if the value is `nil`, the value would still be of optional type:

```swift
if let num = Int("123") {
let sum = num + 1
} else {
// code for the case where nil was found -- may be left out
let num = Int("123")
if num != nil {
// num is of type Int?
}
```

Note that in this form of optional binding, the unwrapped value (here, `num`) is only in scope in the `if` block of code. It is not in scope in the else block or the code outside the `if let` statement.

The `guard-let` option may also be used in the cases where early return is desired:

```swift
Expand All @@ -79,31 +91,15 @@ let sum = num + 1
```

With this form of optional binding, the unwrapped value (here, `num`) is available in the remainder of the scope following the `guard let` statement.

Multiple optional value checks may be combined into a single optional binding statement by separating the checks with commas. Checks may also make use of values bound in earlier checks from the same statement. E.g.

```swift
func numberPlusDigits(_ value: String?) -> Int {
guard
let v = value,
let i = Int(v)
else { return 0 }
return i + v.count
}

numberPlusDigits("123") // return0 126
numberPlusDigits("Hello") // returns 0
numberPlusDigits(nil) // returns 0
```

Both the `if` and the `guard` form of optional binding also support binding the unwrapped value to a variable instead of a constant through the use of `if var` and `guard var`.

## Comparing optionals

Note that even if the base type of a pair of optionals can be compared using the standard comparison operators, the optionals themselves cannot be compared. They can only be checked for equality. two optionals are equal if they are both nil or if the values they wrap are equal within their base types.
Note that even if the base type of a pair of optionals can be compared using the standard comparison operators, the optionals themselves cannot be compared.
They can only be checked for equality.
Two optionals are equal if they are both `nil` or if the values they wrap are equal within their base types.

However, code can of course, be written to perform a custom comparison of two optional values. Below is an example of a `switch` statement that will return `true` only if both optional values are non-nil and the first value is less than the second. To do this it uses the _optional pattern_ `varName?` which only matches non-nil optionals, binding the value inside the optional to the name `varName`:
However, code can be written to perform a custom comparison of two optional values.
Below is an example of a `switch` statement that will return `true` only if both optional values are non-nil and the first value is less than the second.
To do this it uses the _optional pattern_ `varName?` which only matches non-nil optionals, binding the value inside the optional to the name `varName`.

```swift
switch (optionalA, optionalB) {
Expand All @@ -114,7 +110,9 @@ default: return false

## Nil coalescing

Another option for unwrapping exists where it is possible to use a default value if a `nil` is present. This can be done by using the _nil coalescing operator_, `??`. Assuming `x` is an `Int?`, if one writes `let y = x ?? 0`, then Swift will check if x is `nil`. If it is not, then it will unwrap `x` and assign the unwrapped value to `y`, and if `x` _is_ `nil`, then it will assign 0 to `y`.
Another option for unwrapping exists where it is possible to use a [fallback value][fallback] if `nil` is present.
This can be done by using the _nil coalescing operator_, `??`. Assuming `x` is an `Int?`, if one writes `let y = x ?? 0`, then Swift will check if x is `nil`.
If it is not, then it will unwrap `x` and assign the unwrapped value to `y`, and if `x` _is_ `nil`, then it will assign 0 to `y`.

Since `x ?? y` is simply shorthand for `x != nil ? x! : y`, if `x` is not nil, then the expression `y` is not evaluated at all.

Expand All @@ -125,7 +123,10 @@ let k = 42 ?? 0 + 1 // returns 42
let j = nil ?? 0 + 1 // returns 1
```

You can read further about the nil coalescing operator in [A Tour of Swift: Nil-Coalescing Operator][nilcoalescing].
You can read further about the nil coalescing operator in [A Tour of Swift: Nil-Coalescing Operator][nil-coalescing].

[optionals]: https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#ID330
[nilcoalescing]: https://docs.swift.org/swift-book/LanguageGuide/BasicOperators.html#ID72
[optionals]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optionals
[nil]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#nil
[optional-binding]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optional-Binding
[nil-coalescing]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/basicoperators/#Nil-Coalescing-Operator
[fallback]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optional-Binding
74 changes: 62 additions & 12 deletions concepts/optionals/introduction.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# Introduction
# About

Swift uses _optionals_ to allow programmers to represent the possible absence of a value. Before attempting to use a value that may not exist, optionals allow the program to check first it it exists, then if it does exist unwrap and use it.
## Optionals

Any type can be made into an optional by appending a `?` onto the end of the type name. So an optional integer would have type `Int?` and an optional string would have type `String?`. Defining constants or variables of optional type and assigning them values is done the same as for values of non-optional types.
Swift uses [_optionals_][optionals] to allow programmers to represent the possible absence of a value.
Optional is a type that can either hold a value or be [`nil`][nil], which represents the absence of a value.
Using an optional requires a program to check if a value does exist before using it after unwrapping it.

Any type can be made into an optional by appending a `?` onto the end of the type name.
So an optional integer would have type `Int?` and an optional string would have type `String?`.
Defining constants or variables of optional type and assigning them values is done the same as for values of non-optional types.

```swift
let x: Int? = 42
var y: String? = "Hello"
y = "Goodbye"
```

You can assign the absence of a value to a variable of optional type by assigning it the special value `nil`. `nil` can be used with all optional types, but `nil`s assigned to two different optional types do not have the same type, and cannot be interchanged or even compared. E.g.
You can assign the absence of a value to a variable of optional type by assigning it the special value `nil`.
`nil` can be used with all optional types, but `nil`s assigned to two different optional types do not have the same type, and cannot be interchanged or even compared.

```swift
let intOpt: Int? = nil
Expand All @@ -23,24 +30,59 @@ intOpt == stringOpt
// Compiler error: Binary operator '==' cannot be applied to operands of type 'Int?' and 'String?'
```

An example of where optionals arise in Swift is in the initialization of `Int`s and `Double`s from strings. For example, you can convert the string `"123"` to an integer by writing `let newInt = Int("123")`. However, if you do this you will find that the type of `newInt` is not `Int`, but rather `Int?`. This is because not all strings can sensibly be converted to `Int`s. What should the result of `Int("123.45")` or `Int("horse")` be. In cases like this, where there is no sensible value to return, the conversion returns `nil`, and so the return type must be `Int?`.
Also note that even though `nil` can be assigned to any optional type, it cannot be assigned to a non-optional type (even if it doesn't actually hold `nil`).
And methods that is expecting a non-optional type cannot be passed an optional type without unwrapping it first.

```swift
var x: Int = 42
var y: Int? = 42

y = x
// Works fine
x = y
// error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'
x + y
// error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'
```

## Using optionals

Because optional types are not the same types as their base types, the two types cannot be used in the same ways. For example:
`Int("123") + 1` results in a compiler error "Value of optional type 'Int?' must be unwrapped to a value of type 'Int'". In order to access the `Int` from the conversion, one must "unwrap" it first.
Because optional types are not the same types as their base types, the two types cannot be used in the same ways.
For example: `Int("123") + 1` results in a compiler error "Value of optional type 'Int?' must be unwrapped to a value of type 'Int'".
This is because the `Int("123")` returns an optional `Int?` type, not an `Int` type, since if the string cannot be converted to an integer, the result will be `nil`.
In order to access the `Int` from the conversion, one must "unwrap" it first.

This is most commonly done in Swift using the `if-let` and `guard-let` constructs for _optional binding_ which check for `nil` and take one code path with the unwrapped value bound to a supplied name if a value exists and taking a different code path if `nil` was found.
This is most commonly done in Swift using the `if-let` and `guard-let` constructs for [_optional binding_][optional-binding] which check for `nil` and take one code path with the unwrapped value bound to a supplied name if a value exists and taking a different code path if `nil` was found.

```swift
if let num = Int("123") {
let sum = num + 1
if let safeNum = Int("123") {
let sum = safeNum + 1
} else {
// code for the case where nil was found -- may be left out
}
```

It is worth noting that the `safeNum` variable has the type `Int` and not `Int?`.
In the example below, `num` is of type `Int?` and `safeNum` is of type `Int`.

```swift
let num = Int("123")
if let safeNum = num {
// num is of type Int
}
```

This _optional binding_ is important because it unwraps (or "removes") the optional type from the value, allowing it to be used as a non-optional value.
If you would just do a conditional check on if the value is `nil`, the value would still be of optional type:

```swift
let num = Int("123")
if num != nil {
// num is of type Int?
}
```

The `guard-let` option may also be used in the cases where early return is desired:

```swift
Expand All @@ -51,13 +93,21 @@ let sum = num + 1

## Comparing optionals

Note that even if the base type of a pair of optionals can be compared using the standard comparison operators, the optionals themselves cannot be compared. They can only be checked for equality. Two optionals are equal if they are both nil or if the values they wrap are equal within their base types.
Note that even if the base type of a pair of optionals can be compared using the standard comparison operators, the optionals themselves cannot be compared.
They can only be checked for equality.
Two optionals are equal if they are both `nil` or if the values they wrap are equal within their base types.

However, code can of course, be written to perform a custom comparison of two optional values. Below is an example of a `switch` statement that will return `true` only if both optional values are non-nil and the first value is less than the second. To do this it uses the _optional pattern_ `varName?` which only matches non-nil optionals, binding the value inside the optional to the name `varName`:
However, code can be written to perform a custom comparison of two optional values.
Below is an example of a `switch` statement that will return `true` only if both optional values are non-nil and the first value is less than the second.
To do this it uses the _optional pattern_ `varName?` which only matches non-nil optionals, binding the value inside the optional to the name `varName`.

```swift
switch (optionalA, optionalB) {
case let (valA?, valB?): return valA < valB
default: return false
}
```

[optionals]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optionals
[nil]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#nil
[optional-binding]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optional-Binding
11 changes: 10 additions & 1 deletion concepts/optionals/links.json
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
[]
[
{
"url": "https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optionals",
"description": "Swift Book: Optionals"
},
{
"url": "https://developer.apple.com/documentation/swift/optional",
"description": "Swift docs: Optional"
}
]
Loading