Functional magic with Swift

Last few days I have been experimenting with functional magic in Swift. Especially how map and flatMaps work. I was so amazed to know how hassle-free my life became after I understood and harnessed the power of functional magic in Swift. I strongly encourage you to do so as well. In this article I have compiled a list of commonly used expressions that can be used in an app.

  • Let's say you want to make an array of UIViews. How would you do it? Add a for loop, declare an empty array and keep adding an individual element to it? Nah! map can do it automatically for you.

    let numberOfViews = 5
    var allViews = (1...numberOfViews).map({ _ in UIView() })
    

  • Now you have all the views and you want to color each of them with color red.

    var coloredViews = allViews.map { (view) -> UIView in
                           view.backgroundColor = .red
                           return view
                       }
    

  • Now say, you don't want to color all views with same color, but rather color them differently based on the index. For the sake of this example, we will color odd indexed views with red color and even indexed views with green color.

        var coloredViewsWithIndex = allViews.enumerated().map { (index, view) -> UIView in
            if index % 2 == 0 {
                view.backgroundColor = .green
            } else {
                view.backgroundColor = .red
            }
            return view
        }
    

  • We have an array of integers and want to apply certain transformations on them, say double every integer

    // First off, we will add an extension on an integer to double the value
    extension Int {
        func double() -> Int {
            return self * 2
        }
    }
    
    // Next, we will apply map function with array of integers to double every value
    let doubledValues = (1...5).map{ $0.double() }
    
    // filter is another magic swift offers. Say now that we have doubledValues array and we want only those values greater than 7. We can apply filter function to eliminate unwanted values
    let filtered = doubledValues.filter{ $0 > 7 }
    

  • Let's say we have dictionary. (It could hold anything, but for the sake of this example let's say keys are of type int and values are of type string)

    let sampleDictionary: [Int: String] = [1: "first", 2: "second", 3: "third"]
    
    // We can convert this dictionary into array of just keys with following line
    let onlyKeys = sampleDictionary.map{ $0 }
    
    // array of values
    let onlyValues = sampleDictionary.map{ $1 }
    

  • We can also modify the individual value by applying map to it

    // Usually when we want to add constant to optional integer, swift raises a compiler error. Following is invalid
    let val: Int? = 0
    let result = val + 2 // Errors out with error saying we need to unwrap val
    
    // We can simply avoid this by directly applying map to val
    let addedValue = val.map{ $0 + 2 }
    
    // Note that addedValue is an optional int since we are doing arithmatic with input value which is also an optional int
    

  • Using flatMap to flatten 2D array into 1D array

    let array2D = [[1], [2, 3], [3, 4, 6, 7]]
    let flattened = array2D.flatMap { $0 } // flattened = [1, 2, 3, 3, 4, 6, 7]
    
    // We can even go further to double every value in array2D array and retain only those values greater than 10
    let flattenedMoreComplicated = array2D.flatMap{ $0.map{ $0 * 2 }.filter{ $0 > 10 } } // flattenedMoreComplicated = [2, 4, 6, 6, 8, 12, 14]
    

  • Using flatMap to flatten 2D array and remove nils

    let array2DWithNil = [[1], [2, 3, nil], [3, 4, 6, nil]]
    let flatArray = array2DWithNil.flatMap{ $0 } // flatArray = [1, 2, 3, nil, 3, 4, 6, nil]
    let flatArrayWithNoNil = flatArray.flatMap{ $0 } // flatArrayWithNoNil = [1, 2, 3, 3, 4, 6]
    

  • Reduce to combine values from an array

    // Sum of integers in an array
    let intsToReduce = [1, 2, 3, 4]
    let totalInts = intsToReduce.reduce(0, +) // totalInts = 10
    
    // combine words in an array
    let stringsToReduce = ["this", "is", "it"]
    let totalStrings = stringsToReduce.reduce("", +) // totalStrings = "thisisit"
    
    

  • Convert array of values with arbitrary type to array of only ints with flatMap

    // We want to convert array of arbitrary value types to an array of ints
    let weirdValues = ["1", "d", "ddd", "2"]
    
    // We will iterate over array and apply Int(val) function to every value. If value if not transformable to int, Int(val) will return nil which will be ignored by flatMap during conversion
    
    let saneValues = weirdValues.flatMap{ Int($0) } // saneValue = [1, 2]
    

  • map can also be used to little more fancy usage

    Say we have a number which we want to convert to description which would say for example, "Total marks are 100". However, we want to show "No marks available" it value of marks is nil. We can accomplish it with a single line as follows

    let marks: Int? = 100
    // If marks is not nil, the code below will print "Total marks are 100". However, if marks is nil - ternary operator will fail and print "No marks available" instead
    let marksDescription = marks.map{ "Total marks are \($0)" } ?? "No marks available"
    print(marksDescription)    
    

If you are using these functions in your application in ways that can be of interest for rest of us, I would love to hear from you. You can contact me directly through an email or Twitter handle @jayeshkawli

References: