Skip to main content

Tutorial 2

Tutorial Questions

  1. Suppose we are to implement a Tic-Tac-Toe game. We will need an ADT to represent the game state. Suppose we want to use TDD to implement this ADT. Think about the tests we should have for the ADT and game.

    1. What are test doubles?
    2. How do you use test doubles with dependency injection?
    3. Test this function, possibly refactoring it to be more testable:
      func sendEmailToUsers(message: String) {
      let userEmails = Database.queryAll(table: "users").map { $0.email }
      EmailService.sendEmail(to: userEmails, message: message)
      }
      (Assume that the Database.queryAll and EmailService.sendEmail methods do exist.)
    4. When is dependency injection useful? When is it not so useful?
  2. Refactor the code snippet below to adhere to the SLAP better.

    import Foundation

    func leftRotate(_ data: UInt32, by amount: Int) -> UInt32 {
    return data << amount | data >> (32 - amount)
    }

    // Taken from https://en.wikipedia.org/wiki/SHA-1#SHA-1_pseudocode
    func sha1(_ message: String) -> String {
    var data = Data(message.utf8)

    var h0: UInt32 = 0x67452301
    var h1: UInt32 = 0xEFCDAB89
    var h2: UInt32 = 0x98BADCFE
    var h3: UInt32 = 0x10325476
    var h4: UInt32 = 0xC3D2E1F0
    let ml = data.count

    data.append(0x80)
    data.append(contentsOf: Array(repeating: 0,
    count: (64 + (55 - ml) % 64) % 64))
    data.append(withUnsafeBytes(of: (ml * 8).bigEndian) { Data($0) })
    assert(data.count.isMultiple(of: 64))

    for chunkIndex in stride(from: 0, to: data.count, by: 64) {
    var words = stride(from: 0, to: 64, by: 4).map { i -> UInt32 in
    let word = data[chunkIndex + i..<chunkIndex + i + 4]
    return word.withUnsafeBytes { bytes in
    bytes.load(as: UInt32.self).bigEndian
    }
    }

    for i in 16..<80 {
    words.append(leftRotate(
    words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16],
    by: 1
    ))
    }

    var a = h0
    var b = h1
    var c = h2
    var d = h3
    var e = h4
    for i in 0..<80 {
    var f: UInt32 = 0
    var k: UInt32 = 0

    switch i {
    case 0..<20:
    f = (b & c) ^ ((~b) & d)
    k = 0x5A827999
    case 20..<40:
    f = b ^ c ^ d
    k = 0x6ED9EBA1
    case 40..<60:
    f = (b & c) ^ (b & d) ^ (c & d)
    k = 0x8F1BBCDC
    default:
    f = b ^ c ^ d
    k = 0xCA62C1D6
    }

    // &+ is like +, but it does not crash when it overflows
    let temp: UInt32 = leftRotate(a, by: 5) &+ f &+ e &+ k &+ words[i]
    e = d
    d = c
    c = leftRotate(b, by: 30)
    b = a
    a = temp
    }

    h0 &+= a
    h1 &+= b
    h2 &+= c
    h3 &+= d
    h4 &+= e
    }
    return String(format: "%02x", h0) +
    String(format: "%02x", h1) +
    String(format: "%02x", h2) +
    String(format: "%02x", h3) +
    String(format: "%02x", h4)
    }
    1. What is the delegate pattern?

    2. Suppose we want to build a user interface for the Sudoku puzzle that we have built in Problem Set 0. We will have a SudokuGridView UI component (that subclasses UIView); this component represents the grid that is displayed to the user. The user can then input numbers in this grid to attempt the puzzle.

      This SudokuGridView contains no domain logic. Instead, it will communicate with a delegate class that we can specify with a delegate property in the class, i.e.

      class SudokuGridView: UIView {
      var delegate: SudokuGridViewDelegate?
      }

      Here, SudokuGridViewDelegate is a protocol that acts as a delegate to SudokuGridView. Suggest methods that should go in this protocol.

      You may see, e.g. UITableViewDelegate for inspiration.

  3. Watch this video on "Functional Core, Imperative Shell": https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell

    (It's only 13 minutes long! It probably can change the way you architect software!)

    Comment on the paradigm used by this video. Some guidelines:

    • What are the advantages proposed by the "functional core, imperative shell" paradigm?
    • In what situations might this paradigm not be applicable?
    • The video mentions that only the functional core needs to be tested; the imperative shell needs no tests. What do you think about this?

    You might recognise this paradigm from a few patterns, such as:

    • the IO monad
    • the actor model
    • the Flux architecture, used in Redux

    If you have time, you might also want to watch this video by the same author: https://www.destroyallsoftware.com/talks/boundaries. It's the same idea described more clearly in 33 minutes, and is also highly recommended!