diff --git a/Package.swift b/Package.swift index c566f2b74..2964cf5b3 100644 --- a/Package.swift +++ b/Package.swift @@ -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", diff --git a/concepts/optionals/.meta/config.json b/concepts/optionals/.meta/config.json index 5f3da48af..c182cda94 100644 --- a/concepts/optionals/.meta/config.json +++ b/concepts/optionals/.meta/config.json @@ -3,5 +3,7 @@ "authors": [ "wneumann" ], - "contributors": [] + "contributors": [ + "meatball133" + ] } diff --git a/concepts/optionals/about.md b/concepts/optionals/about.md index f226b51da..f0a36424e 100644 --- a/concepts/optionals/about.md +++ b/concepts/optionals/about.md @@ -2,9 +2,13 @@ ## 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 @@ -12,7 +16,8 @@ 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 @@ -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 @@ -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) { @@ -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. @@ -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 diff --git a/concepts/optionals/introduction.md b/concepts/optionals/introduction.md index 7a1ce0207..7cdd8d6db 100644 --- a/concepts/optionals/introduction.md +++ b/concepts/optionals/introduction.md @@ -1,8 +1,14 @@ -# 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 @@ -10,7 +16,8 @@ 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 @@ -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 @@ -51,9 +93,13 @@ 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) { @@ -61,3 +107,7 @@ 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 diff --git a/concepts/optionals/links.json b/concepts/optionals/links.json index fe51488c7..5e7adc338 100644 --- a/concepts/optionals/links.json +++ b/concepts/optionals/links.json @@ -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" + } +] diff --git a/exercises/concept/pizza-slices/.docs/after.md b/exercises/concept/pizza-slices/.docs/after.md deleted file mode 100644 index e578cecce..000000000 --- a/exercises/concept/pizza-slices/.docs/after.md +++ /dev/null @@ -1,131 +0,0 @@ -# After - -## 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. - -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. - -```swift -let intOpt: Int? = nil -let stringOpt: String? = nil - -intOpt = stringOpt -// Compiler error: Cannot assign value of type 'String?' to type 'Int?' - -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. - -```swift -var a: Int? -a ?? 0 // evaluates to 0 - -var b = nil // Compiler error: 'nil' requires a contextual type -``` - -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. - -```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). -``` - -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: - -```swift -let num = Int("123") -let sum : Int -if num != nil { - sum = num! + 1 -} -``` - -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. - -```swift -if let num = Int("123") { - let sum = num + 1 - … -} else { - // code for the case where nil was found -- may be left out -} -``` - -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 -guard let num = Int("123") else { return nil } -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. - -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`: - -```swift -switch (optionalA, optionalB) { -case let (valA?, valB?): return valA < valB -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`. - -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. - -Finally, it should be noted that the nil coalescing operator is right associative, which can lead to surprising results for the unaware. - -```swift -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]. - -[optionals]: https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#ID330 -[nilcoalescing]: https://docs.swift.org/swift-book/LanguageGuide/BasicOperators.html#ID72 diff --git a/exercises/concept/pizza-slices/.docs/hints.md b/exercises/concept/pizza-slices/.docs/hints.md index 9fd1f1c99..14a7429e7 100644 --- a/exercises/concept/pizza-slices/.docs/hints.md +++ b/exercises/concept/pizza-slices/.docs/hints.md @@ -2,7 +2,7 @@ ## General -- Read about [optionals][optionals]and about [conditionals][conditionals] in Apple's _A Tour of Swift_ . +- Read about [optionals][optionals] and about [conditionals][conditionals] in Apple's _A Tour of Swift_. ## 1. Write a function to compute slice sizes which returns nil for invalid input. @@ -17,5 +17,5 @@ - You cannot compare `nil` directly with non-nil values. - You can use switch statements and optional patterns to compare optional values. -[optionals]: https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#ID330 -[conditionals]: https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html#ID127 +[optionals]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Optionals +[conditionals]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/controlflow/#Conditional-Statements diff --git a/exercises/concept/pizza-slices/.docs/instructions.md b/exercises/concept/pizza-slices/.docs/instructions.md index 99f8b685f..b7e3ee465 100644 --- a/exercises/concept/pizza-slices/.docs/instructions.md +++ b/exercises/concept/pizza-slices/.docs/instructions.md @@ -4,30 +4,38 @@ You have a number of pizza slice shops in your town and you want to write a weba ## 1. Write a function to compute slice sizes which returns nil for invalid input. -Implement the function, `sliceSize(diameter: Double?, slices: Int?) -> Double?`, which, given the diameter of a pizza and the number of slices per pizza returns the area of a slice. For negative diameters and for number of slices less than 1, return nil, as there is no such thing as a pizza with negative diameter and no way to slice a pizza into fewer than 1 slice. If either parameter is `nil`, also return `nil` +Implement the function, `sliceSize(diameter: Double?, slices: Int?) -> Double?`, which, given the diameter of a pizza and the number of slices per pizza returns the area of a slice. +For negative diameters and for number of slices less than 1, return `nil`, as there is no such thing as a pizza with negative diameter and no way to slice a pizza into fewer than 1 slice. +If either parameter is `nil`, also return `nil` ```swift sliceSize(diameter: 16, slices: 12) -// => 16.75516081914556 +// Returns 16.75516081914556 sliceSize(diameter: nil, slices: 8) -// => nil +// returns nil ``` ## 2. Process input from your web application to determine the larger slice. -You web application will pass four strings to your function, `biggestSlice(diameterA: String, slicesA: String, diameterB: String, slicesB: String) -> String`. The first and second strings are the diameter and number of slices of the first pizza respectively, and the third and fourth are the diameter and number of slices of the second pizza respectively. +You web application will pass four strings to your function, `biggestSlice(diameterA: String, slicesA: String, diameterB: String, slicesB: String) -> String`. +The first and second strings are the diameter and number of slices of the first pizza respectively, and the third and fourth are the diameter and number of slices of the second pizza respectively. -Implement `biggestSlice` so that it attempts to convert the diameter and number of slices for each pizza into a `Double` and an `Int` respectively if both of these values can be obtained from the strings, use your first function to try to compute the area, otherwise the area for that slice is `nil`. Once the areas of both slices are obtained, compare the two areas using the following rules: +Implement `biggestSlice` so that it attempts to convert the diameter and number of slices for each pizza into a `Double` and an `Int` respectively if both of these values can be obtained from the strings, use your first function to try to compute the area, otherwise the area for that slice is `nil`. +Once the areas of both slices are obtained, compare the two areas using the following rules: 1. If slice A's area is a `Double` and slice B's is `nil`, return "Slice A is bigger". If the reverse is true, return "Slice B is bigger". 2. If both areas are `Double`s, return "Slice A is bigger" or "Slice B is bigger" according to which slice has the greater area. 3. If both areas are `nil`, or if both are `Double`s and they are equal, return "Neither slice is bigger". +~~~~exercism/note +Even if the input result in a slice area of 0, the slice is still considered valid, and should be seen as bigger than a slice with an area of `nil`. +~~~~ + ```swift biggestSlice(diameterA: "10", slicesA: "6", diameterB: "14", slicesB: "12") -// => Slice A is bigger +// returns "Slice A is bigger" biggestSlice(diameterA: "10", slicesA: "6", diameterB: "12", slicesB: "8") -// => Slice B is bigger +// returns "Slice B is bigger" biggestSlice(diameterA: "Pepperoni", slicesA: "6", diameterB: "Sausage", slicesB: "12") -// => Neither slice is bigger +// returns "Neither slice is bigger" ``` diff --git a/exercises/concept/pizza-slices/.docs/introduction.md b/exercises/concept/pizza-slices/.docs/introduction.md index 5aeb2ed99..7cdd8d6db 100644 --- a/exercises/concept/pizza-slices/.docs/introduction.md +++ b/exercises/concept/pizza-slices/.docs/introduction.md @@ -1,10 +1,14 @@ -# Introduction +# About ## 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 @@ -12,7 +16,8 @@ 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 @@ -25,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 @@ -53,9 +93,13 @@ 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) { @@ -63,3 +107,7 @@ 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 diff --git a/exercises/concept/pizza-slices/.meta/design.md b/exercises/concept/pizza-slices/.meta/design.md index c784c448e..fbacd2f7f 100644 --- a/exercises/concept/pizza-slices/.meta/design.md +++ b/exercises/concept/pizza-slices/.meta/design.md @@ -2,7 +2,7 @@ ## Goal -The goal of this exercise is to introduce the student to the concept of Optionals in Swift]. +The goal of this exercise is to introduce the student to the concept of Optionals in Swift. ## Learning objectives diff --git a/exercises/concept/pizza-slices/Package.swift b/exercises/concept/pizza-slices/Package.swift index 0f60f7f70..fbdfed90f 100644 --- a/exercises/concept/pizza-slices/Package.swift +++ b/exercises/concept/pizza-slices/Package.swift @@ -1,28 +1,29 @@ -// swift-tools-version:5.3 +// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( - name: "PizzaSlices", - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "PizzaSlices", - targets: ["PizzaSlices"]), - ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - ], - targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. - .target( - name: "PizzaSlices", - dependencies: []), - .testTarget( - name: "PizzaSlicesTests", - dependencies: ["PizzaSlices"]), - ] + name: "PizzaSlices", + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "PizzaSlices", + targets: ["PizzaSlices"]) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + .package(url: "https://github.com/apple/swift-numerics", from: "1.0.3"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "PizzaSlices", + dependencies: []), + .testTarget( + name: "PizzaSlicesTests", + dependencies: ["PizzaSlices", .product(name: "Numerics", package: "swift-numerics"),]), + ] ) diff --git a/exercises/concept/pizza-slices/Tests/PizzaSlicesTests/PizzaSlicesTests.swift b/exercises/concept/pizza-slices/Tests/PizzaSlicesTests/PizzaSlicesTests.swift index 3a0475c2d..1870c1ae5 100644 --- a/exercises/concept/pizza-slices/Tests/PizzaSlicesTests/PizzaSlicesTests.swift +++ b/exercises/concept/pizza-slices/Tests/PizzaSlicesTests/PizzaSlicesTests.swift @@ -1,74 +1,82 @@ -import XCTest +import Testing +import Foundation +import Numerics @testable import PizzaSlices -final class PizzaSlicesTests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false - func testSliceNormal() throws { - let size = try XCTUnwrap(sliceSize(diameter: 16, slices: 10)) - XCTAssertEqual(size, Double.pi * 6.4, accuracy: 0.01) +@Suite struct PizzaSlicesTests { + @Test("A normal slice") + func testSliceNormal() { + let size = sliceSize(diameter: 16, slices: 10) + #expect(size!.isApproximatelyEqual(to: Double.pi * 6.4, relativeTolerance: 0.01)) } - func testSliceNilDiameter() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertNil(sliceSize(diameter: nil, slices: 10)) + @Test("A slice with nil diameter", .enabled(if: RUNALL)) + func testSliceNilDiameter() { + let size = sliceSize(diameter: nil, slices: 10) + #expect(size == nil) } - func testSliceNilSlices() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertNil(sliceSize(diameter: 16, slices: nil)) + @Test("A slice with nil slices", .enabled(if: RUNALL)) + func testSliceNilSlices() { + let size = sliceSize(diameter: 16, slices: nil) + #expect(size == nil) } - func testSliceBadDiameter() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertNil(sliceSize(diameter: -16, slices: 10)) + @Test("A slice with invalid diameter", .enabled(if: RUNALL)) + func testSliceBadDiameter() { + let size = sliceSize(diameter: -16, slices: 10) + #expect(size == nil) } - func testSliceBadSlices() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertNil(sliceSize(diameter: 16, slices: 0)) + @Test("A slice with invalid slices", .enabled(if: RUNALL)) + func testSliceBadSlices() { + let size = sliceSize(diameter: 16, slices: 0) + #expect(size == nil) } - func testABiggest() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("When slice A is bigger", .enabled(if: RUNALL)) + func testABiggest() { let biggest = biggestSlice(diameterA: "16", slicesA: "8", diameterB: "12", slicesB: "6") - XCTAssertEqual(biggest, "Slice A is bigger") + #expect(biggest == "Slice A is bigger") } - func testBBiggest() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + + @Test("When slice B is bigger", .enabled(if: RUNALL)) + func testBBiggest() { let biggest = biggestSlice(diameterA: "16", slicesA: "10", diameterB: "18", slicesB: "12") - XCTAssertEqual(biggest, "Slice B is bigger") + #expect(biggest == "Slice B is bigger") } - func testBothSame() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("When both slices are the same", .enabled(if: RUNALL)) + func testBothSame() { let biggest = biggestSlice(diameterA: "16", slicesA: "10", diameterB: "16", slicesB: "10") - XCTAssertEqual(biggest, "Neither slice is bigger") + #expect(biggest == "Neither slice is bigger") } - func testANil() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("When A is nil", .enabled(if: RUNALL)) + func testANil() { let biggest = biggestSlice(diameterA: "-16", slicesA: "8", diameterB: "12", slicesB: "6") - XCTAssertEqual(biggest, "Slice B is bigger") + #expect(biggest == "Slice B is bigger") } - func testBNil() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("When B is nil", .enabled(if: RUNALL)) + func testBNil() { let biggest = biggestSlice(diameterA: "16", slicesA: "8", diameterB: "-18", slicesB: "12") - XCTAssertEqual(biggest, "Slice A is bigger") + #expect(biggest == "Slice A is bigger") } - func testBothNil() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("When both are nil", .enabled(if: RUNALL)) + func testBothNil() { let biggest = biggestSlice(diameterA: "16", slicesA: "-8", diameterB: "16 inches", slicesB: "8") - XCTAssertEqual(biggest, "Neither slice is bigger") + #expect(biggest == "Neither slice is bigger") } - func testZeroIsValid() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("That zero is valid", .enabled(if: RUNALL)) + func testZeroIsValid() { let biggest = biggestSlice(diameterA: "0", slicesA: "8", diameterB: "16 inches", slicesB: "8") - XCTAssertEqual(biggest, "Slice A is bigger") + #expect(biggest == "Slice A is bigger") } }