Swift Burning Brain Gymnastics (iv)-MAP and FlatMap

Source: Internet
Author: User
Tags closure


Objective


Swift is actually much more complex than objective-c, and Swift incorporates a number of new features compared to Objective-c, born in the 80 's. It also makes it more difficult for us to learn to master the language. But everything is worth it, Swift compared to Objective-c, the program written out more safe, more concise, and ultimately improve our efficiency and quality.



There are a lot of Swift-related learning materials, and I want to introduce some of its features from another perspective, which I call "brain-burning gymnastics." What do you mean? Is that we specialize in some of the more cost-brain language details to learn. To achieve a more in-depth understanding of the Swift language through "burning the brain" to think.



This is the fourth section of gymnastics, please be ready to exercise before practicing, keep your mind awake.



I always thought I understoodmapflatMap. But until I saw someone say, "a type that implements theflatMapmethod is actually monad." "I found this familiar things become unfamiliar, this section of the burning brain gymnastics intends to be more detailed introductionmapandflatMap, in order to introduce the next section monad to do bedding."


Prepare for movement: basics




In the arraymapAndflatMap


An array of arrays ofmapelements are transformed into some sort of rule, for example:


 
let arr = [1, 2, 4]
// arr = [1, 2, 4]

let brr = arr.map {
    "No." + String($0)
}
// brr = ["No.1", "No.2", "No.4"]


flatMapandmapwhere is the difference? We can compare their definitions. To facilitate reading, I deleted the definitions@noescapethrowsandrethrowskeywords, if you have questions about these keywords, you can check the previous issue of burning brain:


				
     
extension SequenceType {
    public func map<T>(transform: (Self.Generator.Element) -> T) 
         -> [T]
}

extension SequenceType {
    public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) -> S) 
         -> [S.Generator.Element]
}

extension SequenceType {
    public func flatMap<T>(transform: (Self.Generator.Element) -> T?) 
         -> [T]
} 


As we can see,mapthere is only one definition, andflatMapthe definition has two overloaded functions, both of which accept a closure as an argument and return an array. But the difference is that closures are defined differently.



The definition of the first function closure is: And(Self.Generator.Element) -> Shere S is defined as:S : SequenceType. So it is a closure that accepts an array element and then outputs aSequenceTypetype of element. Interestingly, the result of theflatMapfinal execution is not anSequenceTypearray, butSequenceTypean array of internal elements that are otherwise composed, namely:[S.Generator.Element].



Isn't it a little dizzy? Look at the sample code, which is quite clear:


 
let arr = [[1, 2, 3], [6, 5, 4]]
let brr = arr.flatMap {
    $0
}
// brr = [1, 2, 3, 6, 5, 4]


Did you see it? In this example, when the array arr is calledflatMap, the elements[1, 2, 3][6, 5, 4]are passed into the closure and are returned directly as a result. However, the final result is a new array that consists of the elements in both arrays:[1, 2, 3, 6, 5, 4].



It is important to note that the entireflatMapmethod can be disassembled into two steps:


    • The first stepmap, like a method, is to transform the element into some sort of rule.
    • The second step, executing theflattenmethod, takes the element one by one in the array out and makes up a new array.


So, just the code is actually equivalent to:


				
     
let arr = [[1, 2, 3], [6, 5, 4]]
let crr = Array(arr.map{ $0 }.flatten())
// crr = [1, 2, 3, 6, 5, 4]


flatMaponce the first overloaded function is finished, let's look at the second type of overload.



In the second overload, the definition of the closure becomes:(Self.Generator.Element) -> T?The return value T is no longer as required in the first overload as the array, and becomes an arbitrary type of Optional. And theflatMapfinal output of the array result, is actually not thisT?type, but theT?type after unpacking, not.Nonethe array of tuples:[T].



Let's just look at the code.


 
let arr: [Int?] = [1, 2, nil, 4, nil, 5]
let brr = arr.flatMap { $0 }
// brr = [1, 2, 4, 5]


In this example, theflatMapnil in the array is discarded, leaving only the non-null value.



In real business, such examples are quite common, such as you want to construct a set of pictures, so you use UIImage's constructor, but this function may fail (the name of the image does not exist), so the return is a Optional UIImage object. Use theflatMapmethod to conveniently place these objects in the. None of them are to be removed. As shown below:


 
let images = (1...6).flatMap {
    UIImage(named: "imageName-\($0)") 
}
Optional in themapAndflatMap


In fact and not only in themapflatMaparray, but also in the Optional exist. Let's look at the definition first:


 
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    case None
    case Some(Wrapped)

    public func map<U>( f: (Wrapped) throws -> U) 
        rethrows -> U?

    public func flatMap<U>( f: (Wrapped) throws -> U?) 
        rethrows -> U?
}


So, for a Optional variable, themapmethod allows it to modify its own value again, and does not care if it is.None. For example:


Let A1:int? = 3let B1 = a1.map{$ * 2}//B1 = 6let a2:int? =
let a1: Int? = 3
let b1 = a1.map{ $0 * 2 }
// b1 = 6

let a2: Int? = nil
let b2 = a2.map{ $0 * 2 }
// b2 = nil
Nillet B2 = a2.map{$ * 2}//b2 = Nil


One more example, we want to turn a string into a nsdate instance, and if we don't usemapthe method, we can only write this:


 
let a1: Int? = 3
let b1 = a1.map{ $0 * 2 }
// b1 = 6

let a2: Int? = nil
let b2 = a2.map{ $0 * 2 }
// b2 = nil


Withmapfunctions, the code becomes shorter and easier to read:


 
var date: NSDate? = ...
var formatted = date == nil ? nil : NSDateFormatter().stringFromDate(date!)


Does it look like it's a feature? When our input is a Optional, and we need to deal with the Optional in logic as nil, then it is appropriatemapto replace the original notation and make the code shorter.



Then when to use the OptionalflatMapmethod? The answer is: When our closure parameters are likely to return nil.



For example, we want to convert a string to Int, but the conversion may fail, and we can use theflatMapmethod as follows:


 
let s: String? = "abc"
let v = s.flatMap { (a: String) -> Int? in
    return Int(a)
}


I also found here more usemapandflatMapexamples, to share to you: http://blog.xebia.com/the-power-of-map-and-flatmap-of-swift-optionals/.


mapAndflatMapThe source code




Talk is cheap. Show me the code.

–linus Torvalds


To get a better understanding, let's go through Apple's open source Swift code and see howmapflatMapit's done.


Array ofmapThe source code


The source address is: Https://github.com/apple/swift/blob/master/stdlib/public/core/Collection.swift, excerpt as follows:


 
public func map<T>(@noescape transform: (Generator.Element) throws -> T)
        rethrows -> [T] {
    let count: Int = numericCast(self.count)
    if count == 0 {
        return []
    }
    
    var result = ContiguousArray<T>()
    result.reserveCapacity(count)
    
    var i = self.startIndex
    
    for _ in 0..<count {
        result.append(try transform(self[i]))
        i = i.successor()
    }
    
    _expectEnd(i, self)
    return Array(result)
}
Array offlatMapThe source code (overloaded function one)


Just to be said,flatMapthere are two overloaded functions in the array. Let's take a look at the first function implementation. The source address is: Https://github.com/apple/swift/blob/master/stdlib/public/core/SequenceAlgorithms.swift.gyb.


 
public func flatMap<S : SequenceType>(
        transform: (${GElement}) throws -> S
    ) rethrows -> [S.${GElement}] {
        var result: [S.${GElement}] = []
        for element in self {
            result.appendContentsOf(try transform(element))
        }
        return result
}


For this code, we can see that it does the following several things:


    1. Constructs aresultnew array named to hold the result.
    2. Iterate over its own elements, and for each element, call the closing conversion function totransformconvert.
    3. The result of the transformation, using theappendContentsOfmethod, puts the result into theresultarray.


In thisappendContentsOfway, the elements in the array are taken out and placed in a new array. Here is a simple example:


 
var arr = [1, 3, 2]
arr.appendContentsOf([4, 5])
// arr = [1, 3, 2, 4, 5]


So thisflatMapmust require that thetransformfunction return aSequenceTypetype, because theappendContentsOfmethod requires aSequenceTypetype parameter.


Array offlatMapThe source code (overloaded function II)


When our closure parameter returns a typeSequenceTypeother than that, it matches the second overloadedflatMapfunction. The following is the source code of the function.


 
public func flatMap<T>(
    @noescape transform: (${GElement}) throws -> T?
    ) rethrows -> [T] {
        var result: [T] = []
        for element in self {
            if let newElement = try transform(element) {
                result.append(newElement)
            }
        }
        return result
}


In the same way, we have the logical rationale for the function:


    1. Constructs aresultnew array named to hold the result. (exactly the same as another overloaded function)
    2. Iterate over its own elements, and for each element, call the closing conversion function totransformconvert. (exactly the same as another overloaded function)
    3. Converts the result, determines whether the result is nil, if not, usesappendthe method, puts the result into theresultarray. (The only difference is where)


Therefore, theflatMapfunction can filter the case that the closure execution result is nil, and only those results that are not empty after conversion are collected.



For this overloadedflatMapfunction, itmapis very similar to the logic of the function, with only one more logic that determines whether it is nil.



So, the question comes up: "What is themappossible and equivalent substitution of an arrayflatMap?" 」



The answer is: whenmapthe closure function returns the result is notSequenceTypethe time. Because of this,flatMapit will be transferred to the overloaded form we are currently discussing. The difference between this type of overloading and the other ismapsimply not to judge the result to be nil.



Here is a sample code that shows:brrand although they arecrrusedmapandflatMapgenerated separately, the results are exactly the same:


 
let arr = [1, 2, 4]
// arr = [1, 2, 4]

let brr = arr.map {
    "No." + String($0)
}
// brr = ["No.1", "No.2", "No.4"]

let crr = arr.flatMap {
    "No." + String($0)
}
// crr = ["No.1", "No.2", "No.4"]
Optional'smapAndflatMapSource


After reading the implementation of the array, let's take a look at the related implementations in Optional. The source address is: Https://github.com/apple/swift/blob/master/stdlib/public/core/Optional.swift, excerpt as follows:


 
/// If `self == nil`, returns `nil`.  
/// Otherwise, returns `f(self!)`.
public func map<U>(@noescape f: (Wrapped) throws -> U) 
        rethrows -> U? {
    switch self {
    case .Some(let y):
        return .Some(try f(y))
    case .None:
        return .None
    }
}

/// Returns `nil` if `self` is `nil`, 
/// `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) 
        rethrows -> U? {
    switch self {
    case .Some(let y):
        return try f(y)
    case .None:
        return .None
    }
}


The two functions of Optional are really strikingly similar, and if you look at only the comments of the two-part function, you can't even see the difference between the two functions.



There are only two possible differences between the two functions:


    1. fFunction one returnsU, and the other returnsU?.
    2. The result of one call is returned directly, and the other will put the result in. Some back inside.


The two functions ultimately guarantee that the return result is Optional. It's just a different place to convert the results to Optional.



It's like my wife said to me, "I like this thing, do you give it to me?" If you don't send it, I'll just swipe your card and buy it! 」。。。 The result of buying things is essentially the same, who pays the same essentially the same, the difference is just who hands it.



Since the OptionalmapflatMapis the same as the essence, why do we have two forms? This is actually designed for the caller to be more convenient. The caller provides a closure function that either returns the result of a Optional or returns a non-Optional result. For the latter, the use of themapmethod, that is, you can continue to convert the results into Optional. The result is Optional, which means we can continue to chain calls and make it easier for us to handle errors.



Let's look at a slightly burned-out code that uses the OptionalflatMapmethod:


 
var arr = [1, 2, 4]
let res = arr.first.flatMap {
    arr.reduce($0, combine: max)
}
`


The function of this code is: to calculate the maximum value of the elements in the array, it is supposed that the maximum value of the direct use of thereducemethod can be. However, there is a special case to consider: the case where the number of elements in the array is 0, in which case there is no maximum value.



We use the OptionalflatMapmethod to deal with this situation. The result of Arr'sfirstmethod is Optional, and when the array is empty, thefirstmethod returns. None, so this code can handle the case where the number of array elements is 0.


Burn your brains.mapAndflatMapAbout naming




There is only a things in computer science:cache invalidation and naming things.

–phil Karlton


One master said that the computer world really is the problem of only two: the first is the cache expiration problem, the second is to take a name. As the last burning part of the article, let's talk about naming it.



Let me mention a few seemingly "irrational" questions:


    • Themapfunction of the array and the function of optinal are verymapdifferent. But why do you callmapthat name?
    • TheflatMapfunction of the array and the function of optinal are veryflatMapdifferent. But why do you callflatMapthat name?
    • The arrayflatMaphas two overloaded functions, two overloaded functions vary enormously, but why are they calledflatMapthis name?


In my opinion, such a name is actually the reason behind, I try to share my understanding. Let's start with the conclusions and then explain. This conclusion comes from: http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures/.


    • The functions of arrays and Optional aremapcalled the same name, because they are all Functor.
    • Arrays and optinalflatMapfunctions are called the same name, because they are all Monad).





Well, I guess you're starting to dozens: "To explain a problem, two new questions have been introduced: Who knows what is Functor and Monad! 」



Do not worry, we first say the rigorous conclusion to help better summarize and generalize, I try to explain the following Functor and Monad.


Functor


The definition of Functor on Wikipedia is very academic. I think of a relatively easy-to-understand definition: the so-called Functor, is that you can apply a function to a "encapsulated value" and get a new "encapsulated value". Normally, we'll call this functionmap.



What is called "encapsulated value"? An array is an encapsulation of values, and Optional is also an encapsulation of values. If you want, you can also encapsulate some values yourself, such as wrapping the results of a network request with a network exception, to make an enum (see below).


 
enum Result<T> {
    case Success(T)
    case Failure(ErrorType)
}


Whether a value can be a "encapsulated value" depends on the collection represented by the type of the value, through themapfunction, whether it can be mapped to a new collection. This new collection also requires the ability to continue usingmapfunctions and mapping to another collection.



We examine this rule with arrays and Optional types, and we find that they are consistent:


    • The array can bemapgenerated by a function that creates a new array, and the new array can continue to use themapfunction.
    • Optional canmapgenerate a new Optional variable through a function, and the new Optional variable can continue to use themapfunction.


Therefore, both arrays and Optional are Functor.


Monad


If you can understand Functor, then Monad is relatively easy. The so-called Monad, like Functor, also applies a function to a "encapsulated value" to get a new "encapsulated value". But the difference is:


    • The function definition of Functor is from "unpackaged value" to "unpackaged value"
    • The function definition of Monad is from "unpackaged value" to "encapsulated value".


Let me give an example to explain:



We have just said that arrays and Optional are Functor because they supportmaptransformations of the set of "encapsulated values" with functions. So, did you notice? In the definition of the map function, both the input parameter and the returned result are not "encapsulated values", but "unpackaged values". What is an "unpackaged value"?


    • For arrays, an "unpackaged value" is an element of an array, and the closure of the map function accepts a single element, returning an element of one.
    • For Optional, the "unpackaged value" is the value of the Optional solution, and the closure of the map function accepts the unpacked value, and the value returned is also the solution.


Here is the sample code for the array, I intentionally added the parameters of the closure, and we'll look at it. We can find thatmapthe closure accepts an INT type, and returns a String type, which is a single element type, not an array.


// The map's closure accepts an Int type and returns a String type, all of which are element types, not arrays.
let arr = [1, 2, 4]
let brr = arr.map {
     (element: Int)-> String in
     "No." + String (element)
} 


Here is the sample code for Optional, and I intentionally added the parameters of the closure. We can see thatmapthe closure accepts the int type and returns the int type, all of which are non-Optional.


// The map's closure accepts an Int type and returns an Int type, all of which are non-Optional.
let tq: Int? = 1
tq.map {(a: Int)-> Int in
     a * 2
} 


We just said that for Monad, the difference between it and Functor is too small to be the same as the closure parameter type. Array is implementedflatMap, it is a kind of Monad, let's lookflatMapat the function definition in the array, we can see that the closure accepts the elements of the array, and returns an array (the encapsulated value).


// The closure accepts the elements of an array and returns an array (the encapsulated value)
let arr = [1, 2, 3]
let brr = arr.flatMap {
     (element: Int)-> [Int] in
     return [element * 2]
} 


Here is theflatMapdefinition in Optional, as we can see that the closure accepts an INT type and returns a Optional (the encapsulated value).


// The closure accepts an Int type and returns an Optional (encapsulated value)
let tq: Int? = 1
tq.flatMap {(a: Int)-> Int? in
     if a% 2 == 0 {
         return a
     } else {
         return nil
     }
} 


So essentially,mapandflatMaprepresenting a class of behavior, we call this kind of behavior Functor and Monad. They differ only in the fact that the parameter return type of the closure function is different. Therefore, we will put the array and Optional the two very different types, plus two implementations of a very different function, but all namedmapandflatMap.


Multiple Optional


We have mentioned multiple Optional in the first section of the burn-in article, and when using it,mapit triggers multiple Optional problems. For example, the following code, the variablebis a two-level nested nil, so it isif letinvalid.


				
     
let tq: Int? = 1
let b = tq.map { (a: Int) -> Int? in
    if a % 2 == 0 {
        return a
    } else {
        return nil
    }
}
if let _ = b {
    print("not nil")
}


The solution is tomapreplace itflatMap.


Summarize


After the discussion, let's summarize:


    • Both arrays and Optional can supportmapandflatMapfunction.
    • The arrayflatMaphas two overloaded implementations, one implementation is equivalent to the firstmapflatten, and the other implementation is used to remove nil in the result.
    • By reading the source code, we have a deeper understandingmapflatMapof the mechanism inside the function.
    • We discussed themapflatMapproblem of naming and finally concluded that if a type is supportedmap, it is a Functor; a type that, if supportedflatMap, indicates that it is a Monad.
    • We discussed themapproblem of multiple Optional caused by improper use.


Swift Burning Brain Gymnastics (iv)-MAP and FlatMap


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.