Last active
July 10, 2019 20:59
-
-
Save tylermilner/01043e40cde9734dd1aec91d5da2cb8d to your computer and use it in GitHub Desktop.
A Swift playground showcasing some use cases for map and flatMap using Cupcakes.
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
import UIKit | |
/// Introduction | |
// This playground showcases many potential use cases of `map` and `flatMap` in Swift (beyond transforming collections). | |
// See this article on Medium for more information: https://medium.com/rocket-fuel/step-up-your-functional-game-map-and-flatmap-tricks-cdc1578fe7bc | |
// To run this playground in Xcode, copy/paste the contents of this file into a new playground (playgrounds are technically directories so GitHub Gist doesn't work well with them). | |
// There are several comment blocks that represent the "old way" of doing things (without `map`/`flatMap`). Feel free to uncomment these to verify functionality. | |
// Development environment: | |
// - Xcode 10.2.1 | |
// - Swift 5 | |
/// Setup | |
// Our model we'll be working with | |
struct Cupcake { | |
let flavor: String | |
func getFlavor() -> String { | |
return flavor | |
} | |
} | |
// Makes printing the structs in the console a bit more readable | |
extension Cupcake: CustomStringConvertible { | |
var description: String { | |
return "\(Cupcake.self)(flavor: \"\(flavor)\")" | |
} | |
} | |
// Create some Cupcakes | |
let vanillaCupcake = Cupcake(flavor: "vanilla") | |
let chocolateCupcake = Cupcake(flavor: "chocolate") | |
let lemonCupcake = Cupcake(flavor: "lemon") | |
let redVelvetCupcake = Cupcake(flavor: "red velvet") | |
let cupcakes = [vanillaCupcake, chocolateCupcake, | |
lemonCupcake, redVelvetCupcake] | |
/// Map | |
// Map - old way | |
//var cupcakeFlavors: [String] = [] | |
//for cupcake in cupcakes { | |
// let cupcakeFlavor = cupcake.getFlavor() | |
// cupcakeFlavors.append(cupcakeFlavor) | |
//} | |
//print(cupcakeFlavors) | |
// Map - new way | |
let cupcakeFlavors = cupcakes.map { $0.getFlavor() } | |
print(cupcakeFlavors) | |
// Map - not good (using map to generate objects instead of transform them) | |
let chocolateCupcakes: [Cupcake] = (0..<4).map { _ in | |
return Cupcake(flavor: "chocolate") | |
} | |
print(chocolateCupcakes) | |
// Mapping with optionals | |
var numberOfCupcakes: Int? = 0 | |
//let newCount = numberOfCupcakes + 2 // error: value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'? | |
let newCount = numberOfCupcakes.map { $0 + 2 } | |
print(newCount as Any) // prints: Optional(2) | |
/// FlatMap | |
// FlatMap - old way | |
//let cupcakeBoxes = [[vanillaCupcake, chocolateCupcake], | |
// [lemonCupcake, redVelvetCupcake]] | |
//var newCupcakeBox: [Cupcake] = [] | |
//for box in cupcakeBoxes { | |
// newCupcakeBox += box | |
//} | |
//print(newCupcakeBox) | |
// FlatMap - new way | |
let cupcakeBoxes = [[vanillaCupcake, chocolateCupcake], | |
[lemonCupcake, redVelvetCupcake]] | |
let newCupcakeBox = cupcakeBoxes.flatMap { $0 } | |
print(newCupcakeBox) | |
// FlatMap with Optionals | |
let featuredCupcake: Cupcake? = Cupcake(flavor: "chocolate") | |
let flavorOfTheDay = featuredCupcake.flatMap { $0.flavor } ?? "vanilla" | |
print(flavorOfTheDay) // prints "chocolate" | |
/// Map Tricks | |
// Add optional subview - old way | |
//let view = UIView() | |
//var optionalView: UIView? | |
// | |
//if let optionalView = optionalView { | |
// view.addSubview(optionalView) | |
//} | |
//print(view.subviews) // prints: [] | |
// Add optional subview - new way | |
let view = UIView() | |
var optionalView: UIView? | |
optionalView.map { view.addSubview($0) } | |
print(view.subviews) // prints: [] | |
// String interpolation - old way | |
//var numCupcakesString = "Unknown number" | |
//if let numberOfCupcakes = numberOfCupcakes { | |
// numCupcakesString = "There are \(numberOfCupcakes) cupcakes in the box." | |
//} | |
//print(numCupcakesString) // prints: There are 0 cupcakes in the box. | |
// String interpolation - old way (better?) | |
//let numCupcakesString = numberOfCupcakes == nil ? "Unknown number" : "There are \(String(describing: numberOfCupcakes)) cupcakes in the box." | |
//print(numCupcakesString) // prints: There are Optional(0) cupcakes in the box. | |
// String interpolation - old way (better, fixed?) | |
//let numCupcakesString = numberOfCupcakes == nil ? "Unknown number" : "There are \(numberOfCupcakes ?? 0) cupcakes in the box." | |
//print(numCupcakesString) // prints: There are 0 cupcakes in the box. | |
// String interpolation - new way | |
let numCupcakesString = numberOfCupcakes.map { "There are \($0) cupcakes in the box." } ?? "Unknown number" | |
print(numCupcakesString) // prints: There are 0 cupcakes in the box. | |
// Shortcut on init - old way | |
//let cupcakeCounts = [1, 5, 7, 4, 3] | |
//let strings = cupcakeCounts.map { String($0) } | |
//print(strings) // prints: ["1", "5", "7", "4", "3"] | |
// Shortcut on init - new way | |
let cupcakeCounts = [1, 5, 7, 4, 3] | |
let strings = cupcakeCounts.map(String.init) | |
print(strings) // prints: ["1", "5", "7", "4", "3"] | |
/// FlatMap Tricks | |
// Filter objects that fail on init - old way | |
//let cupcakeIcons = ["Red", "ic_choc"] | |
// | |
//var images: [UIImage] = [] | |
//for icon in cupcakeIcons { | |
// if let image = UIImage(named: icon) { | |
// images.append(image) | |
// } | |
//} | |
//print(images) // prints: [] | |
// Filter objects that fail on init - new way (map) | |
//let cupcakeIcons = ["Red", "ic_choc"] | |
//let images = cupcakeIcons.map { UIImage(named: $0) } | |
//print("\(images)") // prints: [nil, nil] | |
// Filter objects that fail on init - new way (flatMap) | |
let cupcakeIcons = ["Red", "ic_choc"] | |
let images = cupcakeIcons.compactMap { UIImage(named: $0) } // NOTE: `flatMap` can technically be used here, but `compactMap` is the preferred approach as of Swift 4.1. | |
print(images) // prints: [] | |
// Get data for an onscreen cell - old way | |
//class SomeViewController: UIViewController { | |
// let tableView = UITableView() | |
// let data = ["One", "Two"] | |
// | |
// // ... | |
// | |
// func cellTapped(_ cell: UITableViewCell) { | |
// if let indexPath = tableView.indexPath(for: cell) { | |
// let cellData = data[indexPath.row] | |
// | |
// // Do something with 'cellData'... | |
// } | |
// } | |
//} | |
// Get data for an onscreen cell - new way | |
class SomeViewController: UIViewController { | |
let tableView = UITableView() | |
let data = ["One", "Two"] | |
// ... | |
func cellTapped(_ cell: UITableViewCell) { | |
guard let cellData = tableView.indexPath(for: cell).flatMap({ data[$0.row] }) else { | |
return | |
} | |
// Do something with 'cellData'... | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment