Hi all I am learning Swift, just going through the book provided by Apple on the app store. On page 21 there is some code and for the life of me I cannot get it to work. Just wondered if anybody could shed light. I am pretty sure it's an update thing but if someone could point me or help that would be great. Here is the code taken from the book (yes I have re-typed exactly)
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, condition: lessThanTen)
However when I put the code in it changes it and shows the condition arg in the function call as shown below. I should point out that I have placed a question mark after condition: as I am not sure what data type Int -> Bool is asking for.
The type Int -> Bool is a function type that takes a single argument of type Int, and returns a value of type Bool. In this sense, hasAnyMatches is a higher order function, in that it expects, in addition to an integer array, a function as an argument. Hence, you can send e.g. a function reference (to an (Int) -> Bool function) or a closure as the second argument to hasAnyMatches).
An example follows below, calling hasAnyMatches with 1. a function reference; 2. an anonymous closure; 3. a pre-defined closure:
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
/* 1. function reference: to 'lessThanTen' function */
hasAnyMatches(numbers, condition: lessThanTen)
/* 2. anonymous (trailing) closure: (Int) -> Bool: "integer less than 0?" */
hasAnyMatches(numbers) { myInteger in myInteger < 0 }
hasAnyMatches(numbers) { $0 < 0 } /* short form */
/* 3. pre-defined closure: (Int) -> Bool: "integer larger than 0?" */
let myIntToBoolClosure : (Int) -> Bool = {
myInteger in
return myInteger > 0
}
hasAnyMatches(numbers, condition: myIntToBoolClosure)
Related
I have a functionBuilder
#_functionBuilder
struct MyBuilder {
static func buildBlock(_ numbers: Int...) -> Int {
var result = 0
for number in numbers {
result += number * 2
}
return result
}
}
Function
func myFunc(#MyBuilder builder: () -> Int) -> Int {
builder()
}
use
let a = myFunc {
10
20
}
print(a) // print 60 is work!
but
let b = myFunc {
10
}
print(b) // print 10?
Why is b not 20?
I try add other buildBlock
static func buildBlock(number: Int) -> Int {
return number * 2
}
But not working :(
Any idea?
Any idea?
What is happening in the failing case is that { 10 } is being treated as a closure of type () -> Int directly and the compiler doesn't appear to consider the function builder at all. The code that is produced is simply a function which returns 10.
This appears to a "feature" where the recognition of { 10 } as a simple closure overrides its possible recognition as a use of the function builder. This may just be a compiler issue or worse it might be a language definition problem...
Please head to feedbackassistant.apple.com and file a report.
The accepted answer is correct, it is just passing the value and ignoring the builder.
But it's a bug, not a feature.
It's fixed on Swift 5.3, which is only available on Xcode 12 (currently in beta).
I can have a function to swap the first two elements of an array:
func swapFirstTwo(array: inout [Int]) {
if array.count >= 2 {
array.swapAt(0, 1)
}
}
typealias Swapper = (inout [Int]) -> ()
// And I can have a variable = the function
let swapThem: Swapper = swapFirstTwo
// And it works like this:
var array = [1,2,3]
print(array)
swapThem(&array)
print(array)
// But I'm allergic to Global functions!
// It would be more swifty to have:
extension Array where Element == Int {
mutating func swapFirstTwo2() {
if count >= 2 {
swapAt(0, 1)
}
}
}
typealias Swapper2 = (inout [Int]) -> () -> ()
// But when I do this:
let swapThemAgain: Swapper2 = Array.swapFirstTwo2
// I get the error:
// Partial application of 'mutating' method is not allowed; calling the function has undefined behavior and will be an error in future Swift versions
var array2 = [1,2,3]
print(array2)
array2.swapFirstTwo2()
print(array2)
// This in fact works but I've tried similar things and sometimes they appear to be unstable.
// How can I achieve doing: array2.swapFirstTwo2() without getting the error?
This in fact works but I've tried similar things and sometimes they appear to be unstable.
Also the compiler warning needs to be heeded.
How can I achieve doing: array2.swapFirstTwo2() without getting the warning/error?
The reason why you get a warning (and soon to be an error in Swift 5 mode) on:
extension Array where Element == Int {
mutating func swapFirstTwo2() {
if count >= 2 {
swapAt(0, 1)
}
}
}
typealias Swapper2 = (inout [Int]) -> () -> ()
let swapThemAgain: Swapper2 = Array.swapFirstTwo2
is due to the fact that inout arguments are only valid for the duration of the call they're passed to, and therefore cannot be partially applied.
So if you were to call the returned (inout [Int]) -> () -> () with &array2, you would get back a () -> () which now has an invalid reference to array2. Attempting to call that function will yield undefined behaviour, as you're trying to mutate an inout argument outside of the window where it's valid.
This problem will be fixed if/when unapplied instance methods get flat signatures, as Array.swapFirstTwo2 will instead evaluate to a (inout [Int]) -> ().
But in the mean time, you can workaround the issue by using a closure instead:
typealias Swapper2 = (inout [Int]) -> ()
let swapThemAgain: Swapper2 = { $0.swapFirstTwo2() }
var array2 = [1,2,3]
print(array2) // [1, 2, 3]
array2.swapFirstTwo2()
print(array2) // [2, 1, 3]
In the book "Learning Swift" (Wagner) there's this example about the closures ( closures as parameters in particular):
func firstInNumbers(numbers: [Int], passingTest: (number: Int) -> Bool -> Int? {
for number in numbers {
if passingTest(number: number) {
return number
}
}
return nil
}
let numbers = [1,2,3,4,5]
func greaterThanThree(number: Int) -> Bool {
return number > 3
}
var firstNumber = firstInNumbers(numbers, greaterThanThree)
println(firstNumber)
<code>
So, why the (number: number) syntax in the if statement ?
The second number:
passingTest(number: number)
^^^^^^^
was added because the type of the closure passingTest is (number: Int) -> Bool. See the number: in the type? That specifies the argument label you need to put when invoking the closure. The reason why the parameter label was put there was probably because the author tries to be clearer, but IMO it is already quite clear what the parameter of that closure is doing. Plus, the label number doesn't really add much meaning... So the first number: was really just for "aesthetics". Maybe it makes it easier to understand for beginners?
However, in Swift 4, you become unable to add parameter labels to closure types. In Swift 4, the method would be like this:
func firstInNumbers(numbers: [Int], passingTest: (Int) -> Bool) -> Int? {
for number in numbers {
if passingTest(number) {
return number
}
}
return nil
}
It seems the thing confusing you is that the closure's parameter name, "number", is the same as the variable you are passing into the closure.
//The parameter name "number" here -----------------V
func firstInNumbers(numbers: [Int], passingTest: (number: Int) -> Bool -> Int? {
//And the variable "number" here -\
// V---------------------------/
for number in numbers {
if passingTest(number: number) {
return number
}
}
return nil
}
Perhaps it's easier to tell the difference if you use a different name, like this:
func firstInNumbers(numbers: [Int], passingTest: (number: Int) -> Bool -> Int? {
for n in numbers {
if passingTest(number: n) {
return n
}
}
return nil
}
In Swift 4 closure parameters can't be labeled. Additionally there were a couple small syntax errors in the original post. Correcting the syntax errors, and bringing the closure examples up to Swift 4, the full example would look like this:
func firstInNumbers(numbers: [Int], passingTest: (_ number: Int)->Bool) -> Int? {
for number in numbers {
if passingTest(number) {
return number
}
}
return nil
}
let numbers = [1,2,3,4,5]
func greaterThanThree(_ number: Int) -> Bool {
return number > 3
}
var firstNumber = firstInNumbers(numbers: numbers, passingTest: greaterThanThree)
print(firstNumber)
I know that in the first line, we can use lessThanTen(number: Int) to replace (int), and contidion means a label, but in the third line: * why dont we use if condition : (item) to replace if condition(item), since condition is a label.
Condition is the method that you're receiving on that method, if you look at that method signature:
condition: (int) -> Bool
which means that you're receiving a condition that can be called using an argument of Int type and will return a bool. Anywhere, inside:
hasAnyMatches
you'll be able to use
condition(anyInt)
Now if you look at the method caller:
hasAnyMatches(list: numbers, condition: lessThanTen)
so, you're saying that the 'condition' on 'hasAnyMatches' will be 'lessThanTen'. Which means that in your
if condition(item)
What really is happening is:
if lessThanTen(item)
I hope it made it clearer!
condition is a Bool-returning closure supplied to hasAnyMatches that needs to be called to yield a boolean value. The closure takes a single argument of type Int, which is the same type as the elements of list. Hence, we call the supplied (Int) -> Bool closure on each item, and in case condition applied to an item returns true, we cut the traversal over the list items short and return true from the function.
Using functional programming tecniques, we could make use of a reduce operation on list to compress the implementation of hasAnyMatches:
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
return list.reduce(false) { $0 || condition($1) }
}
Or, even better (allowing exit return as in the original loop), as described by #Hamish in the comments belows (thanks!), using contains
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
return list.contains(where: condition)
}
Example usage:
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
print(hasAnyMatches(list: numbers, condition: lessThanTen)) // true
When a function's return value is another function,there's no way to get the returned function's argument names.Is this a pitfall of swift language?
For example:
func makeTownGrand(budget:Int,condition: (Int)->Bool) -> ((Int,Int)->Int)?
{
guard condition(budget) else {
return nil;
}
func buildRoads(lightsToAdd: Int, toLights: Int) -> Int
{
return toLights+lightsToAdd
}
return buildRoads
}
func evaluateBudget(budget:Int) -> Bool
{
return budget > 10000
}
var stopLights = 0
if let townPlan = makeTownGrand(budget: 30000, condition: evaluateBudget)
{
stopLights = townPlan(3, 8)
}
Be mindful of townPlan,townPlan(lightsToAdd: 3, toLights: 8) would be much more sensible to townPlan(3, 8), right?
You're correct. From the Swift 3 release notes:
Argument labels have been removed from Swift function types... Unapplied references to functions or initializers no longer carry argument labels.
Thus, the type of townPlan, i.e. the type returned from calling makeTownGrand, is (Int,Int) -> Int — and carries no external argument label information.
For a full discussion of the rationale, see https://github.com/apple/swift-evolution/blob/545e7bea606f87a7ff4decf656954b0219e037d3/proposals/0111-remove-arg-label-type-significance.md