Skip to content

Instantly share code, notes, and snippets.

@tylermilner
Last active July 10, 2019 20:59
Show Gist options
  • Save tylermilner/01043e40cde9734dd1aec91d5da2cb8d to your computer and use it in GitHub Desktop.
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.
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