Understanding the Standard Library

studying the video by Paul Hudson

Learning Swift — Day 220(234)

After cloning the Standard Library somewhere on the computer, lets open the swift/stdlib/public/core folder. The .gyb files in there are short for “Generate your boilerplate”.

Bool

Open the Bool.swift file.

Look for func &&, which is this code:

public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows
      -> Bool {
    return lhs ? try rhs() : false
  } 

It is so described in the Documentation preceding it.

Performs a logical AND operation on two Boolean values.

The logical AND operator (&&) combines two Boolean values and returns true if both of the values are true. If either of the values is false, the operator returns false.

This operator uses short-circuit evaluation: the left-hand side (lhs) is evaluated first, and the right-hand side (rhs) is evaluated only if lhs evaluates to true.

The great thing here seems to be that the right hand side of the return part is an @autoclosure, which I am not completely sure what that is. It seems that it is something that we run only we absolutely have to.

The rethrows keyword means, literally, “if my parameter throws, consider me a throwing function, otherwise, consider me a non-throwing function”. Another example for this is the code of the nil-coalescing operator (??):

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T { // 🤯
	switch optional {
		case .some(let value):
			return value
		case .none:
			return try defaultValue()
	}
} 

Now create a blank playground to see all this in action. Remove line 3.

Create an enumeration called Failure that conforms to the Error protocol. Give it two cases, a badNetwork with an associated string value called message and a broken one!

Now create a fetchRemote() throws -> String function, with a comment to show that some very dangerous code could go in here and make the function throw Failure.badNetwork(message: "Firewall error."). Add another function fetchLocal() that returns a String and make it return “Taylor”. Below that add yet another function called fetchUserData. This function will accept a single parameter called closure, which is a closure which can throw and that returns a string. The whole function will also throw. Inside declare a userData constant that will try to run the closure parameter and then print "User data received: \(userData)").

Now, using a do-catch block, try to call the fetchUserData(using:) function passing the fetchLocal argument and, in the first catch call Failure-badNetwork(let message), which will print said message, otherwise catch printing “Fetch error”. Of course, this kind of code will never throw because fetchLocal will never throw so all this do-catch is kind of pointless. Here is where we meet rethrows.

If we change the last throws of fetchUserData to rethrows we get two warnings in the do-catch block, the one saying that we have no calls to throwing function and the other saying that the catch block is unreachable!

Assert

Open Assert.swift and look for the precondition function, this one:

public func precondition(
  _ condition: @autoclosure () -> Bool,
  _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) {
  // Only check in debug and release mode. In release mode just trap.
  if _isDebugAssertConfiguration() {
    if !_fastPath(condition()) {
      _assertionFailure("Precondition failed", message(), file: file, line: line,
        flags: _fatalErrorFlags())
    }
  } else if _isReleaseAssertConfiguration() {
    let error = !condition()
    Builtin.condfail_message(error._value,
      StaticString("precondition failure").unsafeRawPointer)
  }
}

This function checks a necessary condition for making forward progress. We should use this function to detect conditions that must prevent the program from proceeding, even in shipping code.

In Terminal run the grep "precondition(" * | wc -l to look for all the preconditions in Swift. It will find that Swift will unconditionally crash our app even in release mode 256 times!

Create a new playground to test this out. Create a function called average that accepts an array of integer values and returns a double. Inside, declare a sum constant equal to calling the .reduce function on the parameter with a starting value of 0 and an operator of +. Then, make it return the quotient of the Double version of such sum and of the count of the array’s elements.

If we try to pass an empty array as argument to the function call we get nan as result, which means “Not A Number”. It’s an invalid number. To avoid this we can add a precondition to the beginning of our function which checks for scores.count > 0 and if it finds falls, it causes a crash with the message “You must provide at least one score.”.

Going back to the library let’s look at StaticString. It is a string type designed to represent text that is known at compile time. So:

let str1 = "Hello, Dave." // this is good
let str2 = "Hello, \(name)." // this is not good

This can be useful here:

extension UIImage {
	convenience init(bundleName: StaticString) {
		self.init(named: "\(bundleName)")!
	}
}

Another example is to write an extension to NSRegularExpression like this:

extension NSRegularExpression {
    convenience init(_ pattern: StaticString) {
        do {
            try self.init(pattern: "\(pattern)")
        } catch {
            preconditionFailure("Illegal regex: \(pattern).")
        }
    }
}

I have no idea how to use these things but… well … fine!

.map()

How would we write the map function? We may create a generic function called myMap over a generic type <T> inside an extension to the Sequence type. It will accept a single parameter called transform which is a closure that accepts an Element parameter, can throw and returns a T object. This function can itself rethrows and returns an array of T. We declare an empty array of T, loop over self appending the result of the transform(item) closure to the empty array, before returning the array.

This is the implementation of the standard library:

public func map<T>(
    _ transform: (Element) throws -> T
  ) rethrows -> [T] {
    let initialCapacity = underestimatedCount
    var result = ContiguousArray<T>()
    result.reserveCapacity(initialCapacity)

    var iterator = self.makeIterator()

    // Add elements up to the initial capacity without checking for regrowth.
    for _ in 0..<initialCapacity {
      result.append(try transform(iterator.next()!))
    }
    // Add remaining elements, if any.
    while let element = iterator.next() {
      result.append(try transform(element))
    }
    return Array(result)
  }

Obscure, right?!

Swift has three types of arrays: Array, which is the default we use every day. It is very useful because it bridges very well between CocoaTouch and Cocoa’s arrays, ArraySlice, used for short-term use because it has serious memory implications, a[0...5] and ContiguousArray, fastest, with C performance.

If you don’t need NSArray bridge or call Objective-C APIs, ContiguousArray is usually faster, especially for classes.

reserveCapacity() avoids constantly resizing with append(). It preallocates as much space as needed.

Swift uses a technique called geometric growth, which means they double in size every time.

.allSatisfy()

We can check if a collection satisfies a certain predicate by running the .allSatisfy method on it. Open the SequenceAlgorithms.swift file to find out how Apple made this up.

public func allSatisfy(
    _ predicate: (Element) throws -> Bool
  ) rethrows -> Bool {
    return try !contains { try !predicate($0) }
  }

The test returns true if each item passes, but here return true if the item fails the test (!predicate($0)), while the first part returns false as soon as any item returns true for its closure. Translating this for mortals, this line says: return false as soon as any item fails the test.

Code Archaeology

Checking out into the Swift 3.0.2 release branch and going into the SwiftExperimental.swift file, we find this operator:

public func >>><T, U, V>(
	f: @escaping (T) -> U,
	g: @escaping (U) -> V
) -> ((T) -> V) {
	return { g(f($0)) }
} 

Wao! Here is the code in the playground that was explained in the video:

precedencegroup CompositionPrecedence {
    associativity: left
}

infix operator >>>: CompositionPrecedence

public func >>><T, U, V>(
    f: @escaping (T) -> U,
    g: @escaping (U) -> V
) -> ((T) -> V) {
    return { g(f($0)) }
}

func generateRandomNumber(max: Int) -> Int {
    let number = Int.random(in: 0...max)
    print("Using number: \(number)")
    return number
}

func calculateFactors(number: Int) -> [Int] {
    return (1...number).filter { number % $0 == 0 }
}

func reduceToString(numbers: [Int]) -> String {
    return numbers.reduce("Factors: ") {
        $0 + String($1) + " "
    }
}

let combined = generateRandomNumber >>> calculateFactors >>> reduceToString
print(combined(60))

I cannot understand how we pass from declaring an infix operator called >>> to declaring a function called also >>> to finally using it.

The main issue here, for me in all my ignorance, is that if we call generateRandomNumber, T: Int, U: Int, then calculateFactors, U: Int, V: [Int]. All fine until now… but now what? If fis generateRandomNumber and g is calculateFactors, what is returned? Trying this in the playground:

let firstParts = generateRandomNumber >>> calculateFactors

firstParts is declared as having a type of ((Int) -> [Int]), that is a function that accepts an Int parameter and returns an array of Ints, so correctly T -> V. Now, if my calculations are correct, we can proceed and do:

let secondParts = firstParts >>> reduceToString

At this point, T: Int, U: [Int], V: String and, bum!, secondParts is of type ((Int) -> String)! If I do this:

print(secondParts(60)

I get the same proceeding of the combined part. But how to mentally get there? It’s mind-blowing!


So, that’s it for today and I am very happy of having started back in this way! I am eager to learn and to master these things. I wish I could go on with my app but I think the time is not mature enough right now. Also, I would not know where to start to design an icon for that so I would not be able to ship it anyway. Better take one step at a time and not be in a hurry, when I will learn how to do that it will be good!

Thank you so much for reading this first article of the new way!


If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).

If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s books from this Affiliate Link.

Anyways, thank you so much for reading!

Till the next one!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: