map(_:​)’s immutability in Swift

A generic instance method from the CollectionType protocol, map(:), is a higher-order function designed according to the requirements of a ‘first class citizen’ in functional programming (FP). FP is a paradigm like Object Oriented Programming that designates as ‘first class citizens’ anything that 1) may be named by variables, 2) passed as arguments to procedures, 3) be returned as the results of procedures, or 4) may be included in data structures (cf. Structure & Interpretation of Computer Programs, fn. 121). Swift’s map(_:) satisfies all of these FP requirements.

Swift’s map(_:) accepts a single closure as a parameter, returning an array containing the results of ‘mapping’ the given closure over the sequence’s elements in a collection type (i.e., Array, Set, or Dictionary) such that a a bijection or a one-to-one correspondence is formed, even though the return type of the sequence’s elements may differ from that of the original.

Let us take a look at an example. If you apply NSNumberFormatter’s method for localizedStrings, for instance, to the first five elements in Fibonacci’s sequence (i.e., 1, 1, 2, 3, 5), then map(_:)’s closure returns “one”, “one”, “two”, “three”, “five”.

let fibonacciSequence: Array<NSNumber> = [1,1,2,3,5]
let localizedDescriptionOfFibonacciSequence = fibonacciSequence.map { NumberFormatter.localizedString(from: $0, number: .spellOut)}
print(localizedDescriptionOfFibonacciSequence) //["one", "one", "two", "three", "five"]

The full structure of the closure is as follows:

let fibonacciSequence: Array<NSNumber> = [1,1,2,3,5]
// Store map(_:)'s results in a constant
let localizedDescriptionOfFibonacciSequence = fibonacciSequence.map
({
(fibonnaci: NSNumber) -> String in return NumberFormatter.localizedString(from:fibonnaci, number:.spellOut)
})

The syntax that map(_:) takes may be narrated in the following summaries:

  1. Inside of the map(_:) is a closure ({ }).
  2. The closure takes a single sequence element from the collection type (i.e., fibonacci) whose type may be specified by type annotation;
  3. The closure returns the value as signified by the ->.
  4. The return type is signified syntactically after the ->.
  5. The closure seperates separates the argument parameters & return type from the body of the closure with in.
  6. The body returns the results of the operation.

These markers, however, disappear in the shorthand syntax. The shorthand syntax replaces the verbose requirements in 1, 2, 3, 4, 5, or 6 with a single placeholder signified by a $ sign:

let localizedDescriptionOfFibonacciSequence = fibonacciSequence.map { NumberFormatter.localizedString(from: $0, number: .spellOut)}

The $0 iterates over each of the sequence’s elements, applying the localizedString method. The result is an array of each of the sequence’s elements transformed.

The reduction from full syntax to shorthand is easy to askance in a step by step process.

  1. [ 1, 2, 3, 4, 5, 6].map( { (i: Int) -> Int in return i *2 } )
  2. [ 1, 2, 3, 4, 5, 6].map( { i in return i *2 } ) — Remove: (i: Int) -> Int / Add: i
  3. [ 1, 2, 3, 4, 5, 6].map( { i in i *2 } ) — Remove: return
  4. [ 1, 2, 3, 4, 5, 6].map( { $0 *2 } ) — Replace: i in i with $0
  5. [ 1, 2, 3, 4, 5, 6].map() { $0 *2 } — Detach: parentheses
  6. [ 1, 2, 3, 4, 5, 6].map { $0 *2 } — Remove: paratheses

Each of these steps leverages an important aspect of Swift:

  1. No need to store data in a variable / constant locally;
  2. Swift’s compiler both infers the type of values in the array as well as the result of the expression;
  3. Closures whose bodies contain a single expression, return the value of that expression automatically; return is thus superfluous;
  4. Syntactic sugar in Swift provides shorthand names for the arguments to the function;
  5. Trailing closure syntax permits a closure as the last argument to the function, to move outside the parentheses of the functional call;
  6. A function without an argument drops its parentheses.

In linguistics the functionality of a surfacing word derives its grammar from the depths of syntax (i.e., grammar). The determiner who, for instance, is a determiner whose functionality as an island is determined by its derivation in the depths of syntax.

a. Scott likes TJ’s TDAmeritrade account.

b. *Whose does Scott like TDAmeritrade account? — Extraction from a left branch under a noun fails in English.

c. Whose TDAmeritrade account does Scott like? — Extraction succeeds if & only if the entire Noun Phrase (NP) moves (a.k.a, ‘pied-piped’).

a. TJ bought an expensive set of headphones.

b. *How expesnive did TJ buy a set of headphones. — Extraction from a left branch under a noun fails in English.

c. How expensive a set of headphones did TJ buy? — Extraction succeeds if & only if the entire Noun Phrase (NP) moves (a.k.a, ‘pied-piped’).

The extraction of the determiner who thus requires the entire NP to move. Extraction fails in the b-sentences because the extracted expression corresponds to a so-called `left-branch modifier of a noun` in English. In contrast with the b-sentences, the c-sentences succeed because the extracted expressed moves the determiner as a ‘left branch modifier’ of the NP. These contrastive minimal pairs represent the acceptable / unacceptable structure of the depths of the determiner’s syntax, dictating its functionality (i.e., the movement of entire NPs) as its grammar (J. R. Ross (1967), Constraints on Variables in Syntax).

In a similar fashion map(_:) beholds a derivation from the depths of its syntax. It is derivable from both immutablitiy as well as pass-by-value.

Let us start with a few minimal pairs for contrast. Initialize an empty array of type UInt32 in a mutable variable called an anArrayOfIntegers.

var anArrayOfIntegers = [UInt32]()
for i in 1...30 {
anArrayOfIntegers.append(arc4random())
}

The function arc4random() randomizes integers of type UInt32 to the empty initialized array before the appendix but nothing else happens. The variable anArrayOfInteger is not thereafter mutated. Accordingly, there is reason to declare it as such.

let anArrayOfIntegers = [UInt32]()
for i in 1...30 {
anArrayOfIntegers.append(arc4random())
}

The code, however, does not compile. The compiler returns the following error as soon as the append(_:) method is called on the array: “Cannot use mutating member on immutable value: anArrayOfIntegers is a let constant.” Swift, however, does permit a for loop to be stored in a variable, let alone an array. The solution is to generalize the functionality of the for loop.

func for(range:Range<Int>, body:Int->Int) -> [Int] {
var tempArray = [Int]()
for i in range {
tempArray.append(body(i))
}
return tempArray
}
...
let anArrayOfIntegers = for(1...30) { _ in 
arc4random()
}

The functionality of the for loop is generalized into a function. Since functions are first class citizens, a function call can be stored in the immutable declaration anArrayOfIntegers. The for function is limited, however, to returning a single type but may be further generalized to other types in the following way:

func for<Sequence:SequenceType, Result>(sequence:Sequence, body:Sequence.Generator.Element->Result) -> [Result] {
var storageArray = [Result]()
for i in sequence {
storageArray.append(body(i))
}
return storageArray
}

Since the function call’s parameter accepts arguments of different types, the for function can be called on Int, String, etc…

let anArrayOfIntegers = for(1...30) { _ in 
arc4random()
}
let anArrayOfSquares = for(1...30) { x in 
x * x
}
let anArrayOfRepeatedLetters = for(["x", "y", "z"]) { letter in 
letter + letter
}

Little to no difference, however, exists between the for function and the functionality of map(_:) in Swift’s standard library. Hence these immutable declarations can be rewritten in the following way:

let anArrayOfIntegers = map(1...30) { _ in 
arc4random()
}
let anArrayOfSquares = map(1...30) { x in 
x * x
}
let anArrayOfRepeatedLetters = map(["x", "y", "z"]) { letter in 
letter + letter
}

The two primary components of are both immutability as well as pass-by-value. The emphasis on these two components of its ‘grammar’ takes root in an approach to the problem of programming concurrency. Concurrency dictates that strong references to declarations of a type create cycles. These cycles waste the finite resources of a Computer Processing Unit (i.e., CPU). Immutable objects do not contain references so they can only be passed by value or vice versa (i.e., objects passed by value do not contain references because they are only immutable objects). Strong references are avoided. Concurrency operates without any strain on its resources. Thread safety is guaranteed.


Despite the paradigmatic shift that map(_:) represents for Swift, the underlying basis of its functionality is not irreducible; in all reality map(_:) is a count controlled repetition structure. Although its structure be reducible, map(_:) differs from a traditional count controlled repetition structure in many important ways, as is obvious from the above.

One of the advantages of a loop is its diversity; a loop’s input may be an Array, Set or Dictionary but its output may likewise be any one of those; map(_:), however, is limited in this respect; its output is only an Array, even though its input may be any CollectionType (i.e., Array, Set, or Dictionary). Unlike a for-in loop, which may not even so much as save or be saved within a declaration, map(_:) value semantics empowers its results to be stored immediately after execution.

Swift is leaning towards being a value oriented programming language; insomuch as Swift adopts the functional programming paradigm, Swift transcends the limitations of Object Oriented Programming. Swift’s map(_:) is an example one of these language features that transcend OOP.