Created
May 5, 2017 10:05
-
-
Save davidkneely/1d78b5f43d87fa5551a467c308567d15 to your computer and use it in GitHub Desktop.
closures001 talk 2017_5_4 at Hawaii iOS Developer Meetup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: Playground - noun: a place where people can play | |
// Hawaii iOS Developer Meetup | |
// Closures | |
// 2017_5_4 | |
// 6pm | |
import UIKit | |
// Let's define a closure. Questions? Thoughts? | |
/* | |
Closures - "Discrete bundles of functionality." - BNR | |
"Nameless functions" - This function has no name. | |
"First-class functions" - | |
1) Can be passed into other functions as parameters | |
2) Can be returned by functions | |
Captures variables defined within its scope. | |
*/ | |
// Tonight's agenda: | |
/// Part I - Transform a free function into a nameless closure. | |
/// Part II - Compress the closure into a single line for functionality. | |
/// Part III - Functions as return types | |
/// Part IV - Functions as arguments | |
/// Part V - Closures capture Values | |
/// Part VI - Closures as Reference Types | |
/// Part VII - Functional Programming | |
/// Part VIII - Swift's Higher Order Functions: Map | |
/// Part IX - Swift's Higher Order Functions: Filter | |
/// Part X - Swift's Higher Order Functions: Reduce | |
/// Part XI - Mob Programming Challenge!!! | |
/// Part I - Transform a free function into a nameless closure. | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// BNR Swift Programming book | |
// Chapter 13, page 127 | |
let volunteerCounts = [1, 3, 40, 32, 2, 53, 77, 13] | |
func sortAscending(_ i: Int, _ j: Int) -> Bool { | |
return i > j | |
} | |
let volunteersSorted001 = volunteerCounts.sorted(by: sortAscending) | |
// free function sortAscending is applied to volunteerCounts array | |
// Next we introduce the trailing closure syntax | |
// Let's define "Closure Expression Syntax." It's also know as "Trailing Closure Syntax" | |
/* | |
{(parameters) -> return_type in | |
// code | |
} | |
*/ | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// BNR Swift Programming book | |
// Chapter 13, page 129 | |
// Let's convert the normal function into trailing closure syntax in 5 steps | |
func sortAscending002(_ i: Int, _ j: Int) -> Bool { | |
return i > j | |
} | |
// Step 1: Remove the name of the function and the keyword 'func' | |
// NOTE: This code will not compile until we've done all the steps | |
//(_ i: Int, _ j: Int) -> Bool { | |
// | |
// return i > j | |
//} | |
// Step 2: Move the function signature into the curly braces | |
// NOTE: This code will not compile until we've done all the steps | |
//{(_ i: Int, _ j: Int) -> Bool | |
// | |
// return i > j | |
//} | |
// Step 3: Add in the trailing closure syntax with keyword 'in' after the arrow return type | |
// NOTE: This code will not compile until we've done all the steps | |
//{(_ i: Int, _ j: Int) -> Bool in | |
// | |
// return i > j | |
//} | |
// Step 4: Apply the closure to an Array | |
// NOTE: This code will not compile until we've done all the steps | |
//volunteerCounts.sorted(by: {(_ i: Int, _ j: Int) -> Bool in | |
// | |
// return i > j | |
//}) | |
// Step 5: Assign the closure to a constant or variable to accept the return | |
var volunteersSorted002 = volunteerCounts.sorted(by: { | |
(_ i: Int, _ j: Int) -> Bool in | |
return i > j | |
}) | |
/// Part II - Compress the closure into a single line for functionality. | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// BNR Swift Programming book | |
// Chapter 13, page 129 | |
// Next we're going to compress the closure so it looks cleaner and takes up less lines of code in 4 steps | |
// Step 1: Remove all type annotations from the parameters and the return | |
// NOTE: This code will not compile until we've done all the steps | |
//let volunteersSorted003 = volunteerCounts.sorted(by: { | |
// | |
// (_ i: , _ j: ) -> in | |
// | |
// return i < j | |
// | |
//}) | |
// Step 2: Move entire expression onto one line | |
// NOTE: This code will not compile until we've done all the steps | |
//let volunteersSorted003 = volunteerCounts.sorted(by: {(_ i: , _ j: ) -> in return i < j }) | |
// Step 3: Remove the underscores and arrow. | |
// NOTE: This code will not compile until we've done all the steps | |
//let volunteersSorted003 = volunteerCounts.sorted(by: {(i, j) in return i < j }) | |
// Step 4: Remove the keyword 'return' | |
let volunteersSorted003 = volunteerCounts.sorted(by: {(i, j) in i < j }) | |
/// Let's make it even more compact by using swift specific closure shorthand syntax | |
// Update code to use shorthand for arguments (no need to name them anymore) | |
//let volunteersSorted004 = volunteerCounts.sorted(by: {(i, j) in i < j }) | |
// Step 1: Remove the parameters | |
// NOTE: This code will not compile until we've done all the steps | |
//let volunteersSorted004 = volunteerCounts.sorted(by: { in i < j }) | |
// Step 2: Remove the the keyword 'in' | |
// NOTE: This code will not compile until we've done all the steps | |
//let volunteersSorted004 = volunteerCounts.sorted(by: { i < j }) | |
// Step 3: Rename i and j with (swift specific) closure shorthand | |
let volunteersSorted004 = volunteerCounts.sorted(by: { $0 < $1 }) // with arrays only use $0 | |
/// And finally, if the closure is passed to a function's final argument, it can be written in line | |
//let volunteersSorted005 = volunteerCounts.sorted(by: { $0 < $1 }) | |
// Step 1: Remove the argument name 'by' | |
// NOTE: This code will not compile until we've done all the steps | |
//let volunteersSorted005 = volunteerCounts.sorted({ $0 < $1 }) | |
// Step 2: Remove the parens that surround the closure | |
// NOTE: This code will not compile until we've done all the steps | |
let volunteersSorted005 = volunteerCounts.sorted { $0 < $1 } // can be many arguments in the function call, but must be the last in the list. | |
// question, how can we remove the parens? If it's the last parameter we can remove named parameter. | |
/// Part III - Functions as return types | |
func makeTownGrand() -> (Int, Int) -> Int { // anything after the first arrow is a function | |
func buildRoads(byAddingLights lights: Int, toExistingLights existingLights: Int) -> Int { | |
return lights + existingLights | |
} | |
return buildRoads | |
} | |
// function makeTownGrand takes no arguments | |
// makeTownGrand returns a function that takes two arguments | |
// that returned function returns an integer. | |
// buildRoads is implemented in the function makeTownGrand | |
// buildRoads is a nested function | |
// no need to express the implementation details to outer function | |
// buildRoads must conform to function definition expressed in makeTownGrand function definition. | |
// this means that buildRoads must take two Ints and return an Int | |
// Now let's test out this function makeTownGrand | |
var stoplights = 4 | |
let townPlanByAddingLightsToExistingLights = makeTownGrand() // assigns function as return type | |
townPlanByAddingLightsToExistingLights(4, stoplights) // runs the returned function with arguments | |
print("Knowhere has \(stoplights) stoplights" | |
/// Part IV - Functions as arguments | |
// Adds 'budget' and 'condition' parameters to outer function makeTownGrand | |
// 'condition' argument will take a function as we can see in the method signature | |
// This is why we can pass in the function evaluate with no parameter because it returns a Bool | |
// Sets up conditional check on 'budget' | |
// NOTE: The return type is now an optional because the function might return nil | |
func makeTownGrand(withBudget budget: Int, condition: (Int) -> Bool) -> ((Int, Int) -> Int)? { // one closure to another closure | |
if condition(budget) { // Adds in conditional | |
func buildRoads(byAddingLights lights: Int, toExistingLights existingLights: Int) -> Int { | |
return lights + existingLights | |
} | |
return buildRoads | |
} else { // more conditional code | |
return nil | |
} | |
} | |
func evaluate(budget: Int) -> Bool { | |
return budget > 10_000 | |
} | |
var stoplights001 = 4 | |
if let townPlanByAddingLightsToExistingLights002 = makeTownGrand(withBudget: 1_000, condition: evaluate) { // Do you know why budget does not need a passed in parameter? Because it's a function! | |
stoplights001 = townPlanByAddingLightsToExistingLights002(4, stoplights001) | |
} | |
// Update the budget to get more roads by uncommenting the next function: | |
//if let townPlanByAddingLightsToExistingLights003 = makeTownGrand(withBudget: 100_000, condition: evaluate) { // I am unclear on why budget does not need a passed in parameter | |
// | |
// stoplights001 = townPlanByAddingLightsToExistingLights003(4, stoplights001) | |
//} | |
/// Part V - Closures capture Values | |
// populationTracker(growth: Int) is a nested function | |
// populationTracker(growth: Int) captures the initialPopulation from the enclosing function | |
// makePopulationTracker(forInitialPoplulation:) takes a single argument | |
// population is a running count of people in town | |
func makePoplulationTracker(forInitialPoplulation population: Int) -> (Int) -> Int { | |
var totalPopulation = population | |
func populationTracker(growth: Int) -> Int { | |
totalPopulation += growth | |
return totalPopulation | |
} | |
return populationTracker | |
} | |
var currentPopulation = 5_422 | |
let growBy = makePoplulationTracker(forInitialPoplulation: currentPopulation) | |
growBy(500) // function is keeping internal count going | |
growBy(500) | |
growBy(500) | |
currentPopulation = growBy(500) // assign to var when you are finished | |
/// Part VI - Closures as Reference Types | |
// When you assign a closure to a constant or variable, you are setting that constant or variable to POINT TO a function. | |
// You are not creating a distinct copy of that function. | |
// BEWARE: Any information captured by the function's scope will be changed if you call the function via a new constant or variable. | |
let anotherGrowBy = growBy | |
anotherGrowBy(500) // still referncing the captured population variable in growBy | |
var bigCityPopulation = 4_061_981 | |
let bigCityGrowBy = makePoplulationTracker(forInitialPoplulation: bigCityPopulation) | |
bigCityPopulation = bigCityGrowBy(10_000) | |
currentPopulation // shows that the two populations are distinct from each other | |
/// Part VII - Functional Programming | |
/* | |
Tenents of Functional Programming: (Jessica Kerr) | |
Data In, Data Out | |
*/ | |
// Discussion time | |
// Questions? Comments? | |
/// Part VIII - Swift's Higher Order Functions: Map | |
// Use map to transform an array's contents (not in place!) | |
// example: Square all elements in the array. | |
let precintPopulation = [1244, 2021, 2157] | |
let projectedPopulations = precintPopulation.map { | |
(population: Int) -> Int in | |
return population * 2 | |
} | |
/// Part IX - Swift's Higher Order Functions: Filter | |
// Use filter to select certain elements from the input array and discard the rest (not in place!) | |
// example: Take out all the odd numbers in the array. | |
let bigProjections = projectedPopulations.filter { | |
(projection: Int) -> Bool in | |
return projection > 4000 | |
} | |
/// Part X - Swift's Higher Order Functions: Reduce | |
// Use reduce to take all of the values in the input array and produce a single value (not in place!) | |
// example: Add up all the elements in the array and print out their sum. | |
let totalProjection = projectedPopulations.reduce(0) { | |
(accumulatedProjection: Int, precinctProjection: Int) -> Int in | |
return accumulatedProjection + precinctProjection | |
} | |
//BONUS: Transform each of the preceding higher-order functions to closure shorthand. Can you get them to fit on one line? | |
// MOB PROGRAMMING CHALLENGE: | |
// Take the following imperative code and update it to use higher order functions to achieve the same result. | |
// Bonus points for having zero state within the method. | |
// Bonus points for doing ZERO assignment statements :) | |
/// Squares all elements in Array, then removes the odd numbers, and then adds up all remaining numbers. | |
/// :param: None | |
/// :return: Int | |
func squareThenTakeOutOddsThenReturnSum() -> Int { | |
let inputArray = [1, 3, 40, 32, 2, 53, 77, 13] | |
var tempArray = [Int]() | |
var tempArray2 = [Int]() | |
var returnInt = 0 | |
// Square the numbers | |
for index in 0...inputArray.count - 1 { | |
let returnElement = inputArray[index] * inputArray[index] | |
tempArray.append(returnElement) | |
} | |
// Remove the odds | |
for index in 0...tempArray.count - 1 { | |
if(tempArray[index] % 2 == 0) { | |
tempArray2.append(tempArray[index]) | |
} | |
} | |
// Add up the elements in the list | |
for index in 0...tempArray2.count - 1 { | |
returnInt = returnInt + tempArray2[index] | |
} | |
// return the sum | |
return returnInt | |
} | |
squareThenTakeOutOddsThenReturnSum() | |
// SOLUTION WE CAME UP WITH IN 15 MINUTES: | |
import UIKit | |
/// Squares all elements in Array, then removes the odd numbers, and then adds up all remaining numbers. | |
/// :param: None | |
/// :return: Int | |
func squareThenTakeOutOddsThenReturnSum() -> Int { | |
let inputArray = [1, 3, 40, 32, 2, 53, 77, 13] | |
let temp1 = inputArray.map({$0 * $0}).filter({$0 % 2 == 0}).reduce(0){($0+$1)} | |
return temp1 | |
} | |
squareThenTakeOutOddsThenReturnSum() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment