A blog exploring functional programming and Swift.

Point Freebies: Swift Concurrency and More

Wednesday Sep 15, 2021

We’re celebrating the release of Xcode 13 by making all of our WWDC 2021 videos free! Explore SwiftUI’s new .refreshable and @FocusState features, both in the context of vanilla SwiftUI and the Composable Architecture, and learn how to build a map-powered application from scratch using the new .searchable API:



.refreshable

iOS 15’s new .refreshable API is a great example of how the conciseness of SwiftUI’s declarative syntax and Swift’s new concurrency tools can pack a huge punch in a small package. We begin by exploring how the API works in a vanilla SwiftUI application, including how to cancel in-flight asynchronous work.

Then we show how the API works in the Composable Architecture. At first it’s not clear how to use .refreshable with the Composable Architecture because the library did not immediately have support for any of Swift’s new concurrency tools. Luckily the library is extensible enough that we were able to add support for .refreshable without a single change to the internals of the library.

Watch part 1 and part 2 to learn more.

@FocusState

iOS 15 introduced a declarative API for changing and observing focus in SwiftUI applications. By adding some @FocusState to your view and making use of a simple view modifier you can instantly control the focus of UI controls on screen.

However, as soon as you need complex logic to control the focus of your feature, the simplicity starts to break down. Ideally we could model focus state in observable objects, so that we update focus after asynchronous work and write tests, but sadly that is not possible:

class LoginViewModel: ObservableObject {
  @FocusState var field: Field // 🛑 Does not work outside of View

  enum Field { case email, password }
}

So, in the episode we develop techniques that allow us to model focus state in our view models and synchronize that state with the focus state held in views. This allows us to craft nuanced logic to guide how focus changes and we can even write tests.

We also discover that the techniques developed for making vanilla SwiftUI focus state more understandable and testable fit in perfectly with the Composable Architecture. We can model focus state in our domain’s state struct, and then use our new binding helpers to replay changes back and forth between the store and view.

Watch the episode to learn more.

.searchable

Last, but not least, the .searchable API in iOS 15 makes it super easy to introduce a searching interface on top of any view. It even supports quick access suggestion results that can be displayed on top of the view while searching.

We took two episodes (part 1 and part 2) to explore this API, and in the process built a map searching application from scratch. We used Apple’s MKLocalSearchCompleter to load search suggestions as the user types, and MKLocalSearch to search the map for points-of-interest. Along the way we cooked up some tools to help bridge Swift concurrency with the Composable Architecture, and even wrote a full test suite that proves how the application interacts with its complex dependencies.

Watch part 1 and part 2 to learn more.

Check it out today

Jump-start your explorations into some of iOS 15’s most exciting new APIs by watching our deep dives!


The Composable Architecture ❤️ SwiftUI Bindings

Monday Sep 6, 2021

Correction

Due to a problem involving nested bindable state, we have since had to make things slightly less concise by trading dynamic member lookup for a more explicit method: e.g. viewStore.$field is now viewStore.binding(\.$field). For more information on the change see this release and this pull request.

Early this year, we did a series of episodes on “concise forms.” We showed how SwiftUI comes with some amazing tools for handling state through the use of two-way bindings that can be derived from property wrappers like @State and @ObservedObject. For simple forms it almost feels like magic.

We then compared this to the Composable Architecture, which adopts a “unidirectional” data flow, wherein the only way to mutate state is by sending actions to a runtime store, which holds all of the app’s business logic and is responsible for mutating the state inside. From its very first release the Composable Architecture shipped with tools that integrate deeply with SwiftUI applications, including ways of deriving two-way bindings for various SwiftUI controls, by describing a field in state and an action that can mutate it.

This unfortunately means adding an action per mutable field, and logic for handling that action in the reducer, which is boilerplate that really begins to add up.

For example, a settings screen may model its state with the following struct:

struct SettingsState {
  var digest = Digest.daily
  var displayName = ""
  var enableNotifications = false
  var protectMyPosts = false
  var sendEmailNotifications = false
  var sendMobileNotifications = false
}

Each of these fields should be editable, and in the Composable Architecture this means that each field requires a corresponding action that can be sent to the store. Typically this comes in the form of an enum with a case per field:

enum SettingsAction {
  case digestChanged(Digest)
  case displayNameChanged(String)
  case enableNotificationsChanged(Bool)
  case protectMyPostsChanged(Bool)
  case sendEmailNotificationsChanged(Bool)
  case sendMobileNotificationsChanged(Bool)
}

And we’re not even done yet. In the reducer we must now handle each action, which simply replaces the state at each field with a new value:

let settingsReducer = Reducer<
  SettingsState, SettingsAction, SettingsEnvironment
> { state, action, environment in
  switch action {
  case let digestChanged(digest):
    state.digest = digest
    return .none

  case let displayNameChanged(displayName):
    state.displayName = displayName
    return .none

  case let enableNotificationsChanged(isOn):
    state.enableNotifications = isOn
    return .none

  case let protectMyPostsChanged(isOn):
    state.protectMyPosts = isOn
    return .none

  case let sendEmailNotificationsChanged(isOn):
    state.sendEmailNotifications = isOn
    return .none

  case let sendMobileNotificationsChanged(isOn):
    state.sendMobileNotifications = isOn
    return .none
  }
}

And then, in the view, we can derive two-way bindings from the view store to pass them along, which is pretty verbose to do:

Form {
  TextField(
    "Display name",
    text: viewStore.binding(
      get: \.displayName,
      send: SettingsAction.displayNameChanged
    )
  )
  Toggle(
    "Protect my posts",
    isOn: viewStore.binding(
      get: \.protectMyPosts,
      send: SettingsAction.protectMyPostsChanged
    )
  )
  Toggle(
    "Send notifications",
    isOn: viewStore.binding(
      get: \.sendNotifications,
      send: SettingsAction.sendNotificationsChanged
    )
  )

  if viewStore.sendNotifications {
    Toggle(
      "Mobile",
      isOn: viewStore.binding(
        get: \.sendMobileNotifications,
        send: SettingsAction.sendMobileNotificationsChanged
      )
    )
    Toggle(
      "Email",
      isOn: viewStore.binding(
        get: \.sendEmailNotifications,
        send: SettingsAction.sendEmailNotificationsChanged
      )
    )
    Picker(
      "Top posts digest",
      selection: viewStore.binding(
        get: \.digest,
        send: SettingsAction.digestChanged
      )
    ) {
      ForEach(Digest.allCases, id: \.self) { digest in
        Text(digest.rawValue)
      }
    }
  }
}
.alert(
  item: viewStore.binding(
    get: \.alert,
    send: SettingsAction.dismissAlert
  )
) { alert in
  Alert(title: Text(alert.title))
}

This is a ton of boilerplate for something that should be simple. Luckily, we were able to employ some advanced Swift techniques, like type erasure and key paths, to dramatically eliminate this boilerplate by introducing BindingAction. It allowed us to collapse all of these field-mutating actions into a single case that holds a FormAction generic over the reducer’s root SettingsState:

enum SettingsAction {
  case binding(BindingAction<SettingsState>)
}

Then we can collapse all of the field mutations in the reducer by instead tacking on the binding method, which performs all these field mutations for us:

let settingsReducer = Reducer<
  SettingsState, SettingsAction, SettingsEnvironment
> { state, action, environment in
  switch action {
  case .binding:
    return .none
  }
}
.binding(action: /SettingsAction.binding)

And finally, we used a view store helper that simplified the work of deriving a binding by specifying the a path and binding action:

TextField(
  "Display name",
  text: viewStore.binding(keyPath: \.displayName, send: SettingsAction.binding)
)
...

This was overall a huge improvement! We were able to eliminate a ton of boilerplate in action enums and reducers.

However, this was not without a cost. Adding a BindingAction case to your action enum immediately gives your view unfettered access to mutate its state, which goes completely against the grain of the Composable Architecture.

For instance, if we introduce some state that should not be mutated outside the reducer:

struct SettingsState {
  ...
  var isLoading = false
}

Nothing protects it in the view:

.onAppear {
  viewStore.send(.binding(.set(\.isLoading, true)))
}

And so, in our most recent two episodes, we faced this problem head-on, and along the way made things even more concise, by utilizing even more advanced Swift features, including property wrappers and dynamic member lookup. If you’re interested in how, we hope you’ll check them out today:

By introducing the @BindableState property wrapper, it is now possible to safely annotate which fields in state should be bindable:

struct SettingsState: Equatable {
  @BindableState var alert: AlertState? = nil
  @BindableState var digest = Digest.daily
  @BindableState var displayName = ""
  var isLoading = false
  @BindableState var protectMyPosts = false
  @BindableState var sendNotifications = false
  @BindableState var sendMobileNotifications = false
  @BindableState var sendEmailNotifications = false
}

Notably, isLoading is not annotated, and so it can not be mutated directly in the view.

Our action enum can stay mostly the same, except we have also introduced a BindableAction protocol, which can allow us to eliminate even more boilerplate.

enum SettingsAction: BindableAction {
  case binding(BindingAction<SettingsState>)
}

The protocol has a single requirement, which is a static binding method that can bundle up a BindingAction into itself, and because enum cases can conform to such requirements, SettingsAction already conforms!

With this protocol defined, we can simplify the reducer because we no longer have to specify the binding case explicitly:

let settingsReducer = Reducer<
  SettingsState, SettingsAction, SettingsEnvironment
> { state, action, environment in
  switch action {
  case .binding:
    return .none
  }
}
.binding()

But the real gains are seen in the view. Because the action can be inferred, we can adopt a syntax that matches the conciseness of vanilla SwiftUI!

Form {
  TextField("Display name", text: viewStore.$displayName)
  Toggle("Protect my posts", isOn: viewStore.$protectMyPosts)
  Toggle("Send notifications", isOn: viewStore.$sendNotifications)

  if viewStore.sendNotifications {
    Toggle("Mobile", isOn: viewStore.$sendMobileNotifications)
    Toggle("Email", isOn: viewStore.$sendEmailNotifications)
    Picker("Top posts digest", selection: viewStore.$digest) {
      ForEach(Digest.allCases, id: \.self) { digest in
        Text(digest.rawValue)
      }
    }
  }
}
.alert(item: viewStore.$alert) { alert in
  Alert(title: Text(alert.title))
}

That’s over 3x shorter than what we had before!

Say “hi” to safety, and “bye” to even more boilerplate today!

We’ve just released version 0.26.0 of the Composable Architecture, and so you can start simplifying your existing code today. Let us know what you think on Twitter, or start a discussion on GitHub.


Open Sourcing: Custom Dump

Monday Aug 23, 2021

The ability to dump data structures into nicely formatted, human readable strings is important for debugging applications. Swift’s dump function can help with this, but there’s some room for improvement.

That’s why we are excited to announce the open sourcing of Custom Dump, a collection of tools for debugging, diffing, and testing your application’s data structures. It comes with three tools:

  • customDump: dump any data type into a nicely formatted string.
  • diff: visually represent the difference between two values.
  • XCTAssertNoDifference: an alternative to XCTAssertEqual with better failure messages.

Motivation

Swift comes with a wonderful tool for dumping the contents of any value to a string, and it’s called dump. It prints all the fields and sub-fields of a value into a tree-like description:

struct User {
  var favoriteNumbers: [Int]
  var id: Int
  var name: String
}

let user = User(
  favoriteNumbers: [42, 1729],
  id: 2,
  name: "Blob"
)

dump(user)
▿ User
  ▿ favoriteNumbers: 2 elements
    - 42
    - 1729
  - id: 2
  - name: "Blob"

This is really useful, and can be great for building debug tools that visualize the data held in runtime values of our applications, but sometimes its output is not ideal.

For example, dumping dictionaries leads to a verbose output that can be hard to read (also note that the keys are unordered):

dump([1: "one", 2: "two", 3: "three"])
▿ 3 key/value pairs
  ▿ (2 elements)
    - key: 2
    - value: "two"
  ▿ (2 elements)
    - key: 3
    - value: "three"
  ▿ (2 elements)
    - key: 1
    - value: "one"

Similarly enums have a very verbose output:

dump(Result<Int, Error>.success(42))
▿ Swift.Result<Swift.Int, Swift.Error>.success
  - success: 42

It gets even harder to read when dealing with deeply nested structures:

dump([1: Result<User, Error>.success(user)])
▿ 1 key/value pair
  ▿ (2 elements)
    - key: 1
    ▿ value: Swift.Result<User, Swift.Error>.success
      ▿ success: User
        ▿ favoriteNumbers: 2 elements
          - 42
          - 1729
        - id: 2
        - name: "Blob"

There are also times that dump simply does not print useful information, such as enums imported from Objective-C:

import UserNotifications

dump(UNNotificationSetting.disabled)
- __C.UNNotificationSetting

So, while the dump function can be handy, it is often too crude of a tool to use. This is the motivation for the customDump function.

customDump

The customDump function emulates the behavior of dump, but provides a more refined output of nested structures, optimizing for readability. For example, structs are dumped in a format that more closely mimics the struct syntax in Swift, and arrays are dumped with the indices of each element:

import CustomDump

customDump(user)
User(
  favoriteNumbers: [
    [0]: 42,
    [1]: 1729
  ],
  id: 2,
  name: "Blob"
)

Dictionaries are dumped in a more compact format that mimics Swift’s syntax, and automatically orders the keys:

customDump([1: "one", 2: "two", 3: "three"])
[
  1: "one",
  2: "two",
  3: "three"
]

Similarly, enums also dump in a more compact, readable format:

customDump(Result<Int, Error>.success(42))
Result.success(42)

And deeply nested structures have a simplified tree-structure:

customDump([1: Result<User, Error>.success(user)])
[
  1: Result.success(
    User(
      favoriteNumbers: [
        [0]: 42,
        [1]: 1729
      ],
      id: 2,
      name: "Blob"
    )
  )
]

diff

Using the output of the customDump function we can build a very lightweight way to textually diff any two values in Swift:

var other = user
other.favoriteNumbers[1] = 91

print(diff(user, other)!)
  User(
    favoriteNumbers: [
      [0]: 42,
-     [1]: 1729
+     [1]: 91
    ],
    id: 2,
    name: "Blob"
  )

Further, extra work is done to minimize the size of the diff when parts of the structure haven’t changed, such as a single element changing in a large collection:

let users = (1...5).map {
  User(
    favoriteNumbers: [$0],
    id: $0,
    name: "Blob \($0)"
  )
}

var other = users
other.append(
  .init(
    favoriteNumbers: [42, 1729],
    id: 100,
    name: "Blob Sr."
  )
)

print(diff(users, other)!)
  [
    … (4 unchanged),
+   [5]: User(
+     favoriteNumbers: [
+       [0]: 42,
+       [1]: 1729
+     ],
+     id: 100,
+     name: "Blob Sr."
+   )
  ]

For a real world use case we modified Apple’s Landmarks tutorial application to print the before and after state when favoriting a landmark:

  [
    [0]: Landmark(
      id: 1001,
      name: "Turtle Rock",
      park: "Joshua Tree National Park",
      state: "California",
      description: "This very large formation lies south of the large Real Hidden Valley parking lot and immediately adjacent to (south of) the picnic areas.",
-     isFavorite: true,
+     isFavorite: false,
      isFeatured: true,
      category: Category.rivers,
      imageName: "turtlerock",
      coordinates: Coordinates(…)
    ),
    … (11 unchanged)
  ]

XCTAssertNoDifference

The XCTAssertEqual function from XCTest allows you to assert that two values are equal, and if they are not the test suite will fail with a message:

var other = user
other.name += "!"

XCTAssertEqual(user, other)
XCTAssertEqual failed: ("User(favoriteNumbers: [42, 1729], id: 2, name: "Blob")") is not equal to ("User(favoriteNumbers: [42, 1729], id: 2, name: "Blob!")")

Unfortunately this failure message is quite difficult to visually parse and understand. It takes a few moments of hunting through the message to see that the only difference is the exclamation mark at the end of the name. The problem gets worse if the type is more complex, consisting of nested structures and large collections.

This library also ships with an XCTAssertNoDifference function to mitigate these problems. It works like XCTAssertEqual except the failure message uses the nicely formatted diff to show exactly what is different between the two values:

XCTAssertNoDifference(user, other)
XCTAssertNoDifference failed: …

    User(
      favoriteNumbers: […],
      id: 2,
  -   name: "Blob"
  +   name: "Blob!"
    )

(First: -, Second: +)

Case Studies

The Custom Dump library was first conceived as a tool for our other library, the Composable Architecture. That library ships with a debugging helper on reducers that prints the diff of state changes every time an action is sent into the system, as well as an assertion helper that helps you write comprehensive tests on your features, giving you a nicely formatted failure message when an assertion fails.

When we decided to extract that functionality into its own library, Custom Dump, we knew we wanted to make a lot of improvements. The output of the debug and assertion helpers, while helpful, is very verbose. If your feature’s state is large, then every single field and sub-field is printed, even if nothing changed.

We have greatly improved the ergonomics of dumping and diffing in the Custom Dump library, which now the Composable Architecture leverages, and below we have just a few examples of how the ergonomics of the debug and assertion helpers has improved.

Tic-Tac-Toe

The Tic-Tac-Toe example application shows how to build a multi-screen, modularized application with the Composable Architecture.

The state of the application holds a puzzle board, which consists of a two dimension array of X’s and O’s, representing the players’ moves. Previously, the full two dimensional array would be dumped when using the reducer debug helper, even if only a single element was changed. For example, when a cell was tapped:

received action:
  AppAction.newGame(
    NewGameAction.game(
      GameAction.cellTapped(
        row: 1,
        column: 1
      )
    )
  )
  AppState.newGame(
    NewGameState(
      game: GameState(
        board: Three<Three<Optional<Player>>>(
          first: Three<Optional<Player>>(
            first: nil,
            second: nil,
            third: nil
          ),
          second: Three<Optional<Player>>(
            first: nil,
-           second: nil,
+           second: Player.x,
            third: nil
          ),
          third: Three<Optional<Player>>(
            first: nil,
            second: nil,
            third: nil
          )
        ),
-       currentPlayer: Player.x,
+       currentPlayer: Player.o,
        oPlayerName: "Blob Jr.",
        xPlayerName: "Blob Sr."
      ),
      oPlayerName: "Blob Jr.",
      xPlayerName: "Blob Sr."
    )
  )

With the improvements that Custom Dump brings a lot of that state is now collapsed so that we can focus on just the small piece of state that did change:

received action:
  AppAction.newGame(
    NewGameAction.game(
      GameAction.cellTapped(
        row: 1,
        column: 1
      )
    )
  )
  AppState.newGame(
    NewGameState(
      game: GameState(
        board: Three(
          first: Three(…),
          second: Three(
            first: nil,
-           second: nil,
+           second: Player.x,
            third: nil
          ),
          third: Three(…)
        ),
-       currentPlayer: Player.x,
+       currentPlayer: Player.o,
        oPlayerName: "Blob Jr.",
        xPlayerName: "Blob Sr."
      ),
      oPlayerName: "Blob Jr.",
      xPlayerName: "Blob Sr."
    )
  )

Todos

The Todos demo shows how to build a classic application using the Composable Architecture. The state of the todos application can potentially hold many, many todos, and so previously when using the debug helper the entire collection would be dumped to the console.

For example, if we had 4 todos in our list and we checked one of them off, the diff output would be the following 40 lines, which barely fits on the screen at once:

received action:
  AppAction.todo(
    id: UUID(
      uuid: "2294C632-5F71-4F7D-B303-FEFCD0FFB9FD"
    ),
    action: TodoAction.checkBoxToggled
  )
  AppState(
    editMode: EditMode.inactive,
    filter: Filter.all,
    todos: [
      Todo(
        description: "Seltzer",
        id: UUID(
          uuid: "7845CAE9-F98B-492F-A182-886C8328A114"
        ),
        isComplete: false
      ),
      Todo(
        description: "Bread",
        id: UUID(
          uuid: "F333D9BE-2D56-4456-AF06-F74DA098CA88"
        ),
        isComplete: false
      ),
      Todo(
        description: "Eggs",
        id: UUID(
          uuid: "2294C632-5F71-4F7D-B303-FEFCD0FFB9FD"
        ),
-       isComplete: false
+       isComplete: true
      ),
      Todo(
        description: "Milk",
        id: UUID(
          uuid: "065A2A8E-1D61-4DFA-B49B-3D9AB57D1BAC"
        ),
        isComplete: false
      ),
    ]
  )

But now, with the new Custom Dump library, extra work is done to collapse elements of collections that do not change, leading to much shorter, more concise outputs and diffs. Checking off the same todo results in only 18 lines of diff, making it very clear exactly what changed:

received action:
  AppAction.todo(
    id: UUID(4A101EDB-B9BF-4DBD-871D-3D72CBE1E8CE),
    action: TodoAction.checkBoxToggled
  )
  AppState(
    editMode: EditMode.inactive,
    filter: Filter.all,
    todos: [
      … (2 unchanged),
      [2]: Todo(
        description: "Eggs",
        id: UUID(4A101EDB-B9BF-4DBD-871D-3D72CBE1E8CE),
-       isComplete: false
+       isComplete: true
      ),
      [3]: Todo(…)
    ]
  )

isowords

isowords is a word game built in SwiftUI and the Composable Architecture, which we open sourced earlier this year. It is a large application, consisting of many screens and the game domain is quite complex, making use of a 3-dimensional array of values to represent a cube of letters.

Printing the state for every action sent in this application massively bloated our logs. Each action resulted in nearly 2,000 lines being printed to the console, even if just a tiny bit of state changed. The diff is so massive that we have hidden a sample of it below:

Click to see diff!
received action:
  AppAction.currentGame(
    GameFeatureAction.game(
      GameAction.tap(
        UIGestureRecognizerState.UIGestureRecognizerState,
        IndexedCubeFace(
          index: LatticePoint(
            x: Index.one,
            y: Index.two,
            z: Index.two
          ),
          side: Side.left
        )
      )
    )
  )
  AppState(
    game: GameState(
      activeGames: ActiveGamesState(
        savedGames: SavedGamesState(
          dailyChallengeUnlimited: nil,
          unlimited: InProgressGame(
            cubes: Three<Three<Three<Cube>>>(
              rawValue: [
                Three<Three<Cube>>(
                  rawValue: [
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "F",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "G",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "X",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "E",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "C",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "R",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "Z",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "O",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "C",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "C",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "V",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "N",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "F",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "Z",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "T",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "T",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "T",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "X",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "T",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "G",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "I",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "R",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "N",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "I",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "A",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "D",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                  ]
                ),
                Three<Three<Cube>>(
                  rawValue: [
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "R",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "K",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "D",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "V",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "G",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "I",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "G",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "C",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "S",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "I",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "I",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "E",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "N",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "R",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "S",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "E",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "T",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "I",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "R",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "A",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "B",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "M",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "V",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "G",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                  ]
                ),
                Three<Three<Cube>>(
                  rawValue: [
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "O",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "S",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "I",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "S",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "E",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "L",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "R",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "R",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "K",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "H",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "W",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "N",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "O",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "C",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                    Three<Cube>(
                      rawValue: [
                        Cube(
                          left: CubeFace(
                            letter: "N",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "C",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "E",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "K",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "E",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "N",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                        Cube(
                          left: CubeFace(
                            letter: "O",
                            side: Side.left,
                            useCount: 0
                          ),
                          right: CubeFace(
                            letter: "S",
                            side: Side.right,
                            useCount: 0
                          ),
                          top: CubeFace(
                            letter: "S",
                            side: Side.top,
                            useCount: 0
                          ),
                          wasRemoved: false
                        ),
                      ]
                    ),
                  ]
                ),
              ]
            ),
            gameContext: GameContext.solo,
            gameMode: GameMode.unlimited,
            gameStartTime: 2021-08-20T22:08:25Z,
            _language: Language.en,
            moves: Moves(
              rawValue: [
              ]
            ),
            secondsPlayed: 0
          )
        ),
        turnBasedMatches: [
        ]
      ),
      alert: nil,
      bottomMenu: nil,
      cubes: Three<Three<Three<Cube>>>(
        rawValue: [
          Three<Three<Cube>>(
            rawValue: [
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "F",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "G",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "X",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "E",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "C",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "R",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "Z",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "O",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "C",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "C",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "V",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "N",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "F",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "Z",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "T",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "T",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "T",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "X",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "T",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "G",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "I",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "R",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "N",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "I",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "A",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "D",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
            ]
          ),
          Three<Three<Cube>>(
            rawValue: [
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "R",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "K",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "D",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "V",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "G",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "I",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "G",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "C",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "S",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "I",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "I",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "E",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "N",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "R",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "S",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "E",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "T",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "I",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "R",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "A",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "B",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "M",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "V",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "G",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
            ]
          ),
          Three<Three<Cube>>(
            rawValue: [
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "O",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "S",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "I",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "S",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "E",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "L",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "R",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "R",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "K",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "H",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "W",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "N",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "O",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "C",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
              Three<Cube>(
                rawValue: [
                  Cube(
                    left: CubeFace(
                      letter: "N",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "C",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "E",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "K",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "E",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "N",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                  Cube(
                    left: CubeFace(
                      letter: "O",
                      side: Side.left,
                      useCount: 0
                    ),
                    right: CubeFace(
                      letter: "S",
                      side: Side.right,
                      useCount: 0
                    ),
                    top: CubeFace(
                      letter: "S",
                      side: Side.top,
                      useCount: 0
                    ),
                    wasRemoved: false
                  ),
                ]
              ),
            ]
          ),
        ]
      ),
      cubeStartedShakingAt: nil,
      gameContext: GameContext.solo,
      gameCurrentTime: 2021-08-20T22:16:37Z,
      gameMode: GameMode.unlimited,
      gameOver: nil,
      gameStartTime: 2021-08-20T22:08:25Z,
      isDemo: false,
      isGameLoaded: true,
      isOnLowPowerMode: false,
      isPanning: false,
      isSettingsPresented: false,
      isTrayVisible: false,
      language: Language.en,
      moves: Moves(
        rawValue: [
        ]
      ),
-     optimisticallySelectedFace: nil,
+     optimisticallySelectedFace: IndexedCubeFace(
+       index: LatticePoint(
+         x: Index.one,
+         y: Index.two,
+         z: Index.two
+       ),
+       side: Side.left
+     ),
      secondsPlayed: 14,
      selectedWord: [
+       IndexedCubeFace(
+         index: LatticePoint(
+           x: Index.one,
+           y: Index.two,
+           z: Index.two
+         ),
+         side: Side.left
+       ),
      ],
      selectedWordIsValid: false,
      upgradeInterstitial: nil,
      wordSubmitButton: WordSubmitButtonState(
        areReactionsOpen: false,
        favoriteReactions: [
          Reaction(
            rawValue: "😇"
          ),
          Reaction(
            rawValue: "😡"
          ),
          Reaction(
            rawValue: "😭"
          ),
          Reaction(
            rawValue: "😕"
          ),
          Reaction(
            rawValue: "😏"
          ),
          Reaction(
            rawValue: "😈"
          ),
        ],
        isClosing: false,
        isSubmitButtonPressed: false
      )
    ),
    onboarding: nil,
    home: HomeState(
      changelog: nil,
      dailyChallenges: nil,
      hasChangelog: false,
      hasPastTurnBasedGames: false,
      nagBanner: nil,
      route: nil,
      savedGames: SavedGamesState(
        dailyChallengeUnlimited: nil,
        unlimited: InProgressGame(
          cubes: Three<Three<Three<Cube>>>(
            rawValue: [
              Three<Three<Cube>>(
                rawValue: [
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "F",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "G",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "X",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "E",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "C",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "R",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "Z",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "O",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "C",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "C",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "V",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "N",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "F",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "Z",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "T",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "T",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "T",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "X",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "T",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "G",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "I",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "R",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "N",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "I",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "A",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "D",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                ]
              ),
              Three<Three<Cube>>(
                rawValue: [
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "R",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "K",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "D",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "V",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "G",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "I",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "G",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "C",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "S",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "I",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "I",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "E",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "N",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "R",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "S",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "E",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "T",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "I",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "R",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "A",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "B",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "M",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "V",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "G",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                ]
              ),
              Three<Three<Cube>>(
                rawValue: [
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "O",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "S",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "I",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "S",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "E",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "L",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "R",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "R",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "K",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "H",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "W",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "N",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "O",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "C",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                  Three<Cube>(
                    rawValue: [
                      Cube(
                        left: CubeFace(
                          letter: "N",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "C",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "E",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "K",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "E",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "N",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                      Cube(
                        left: CubeFace(
                          letter: "O",
                          side: Side.left,
                          useCount: 0
                        ),
                        right: CubeFace(
                          letter: "S",
                          side: Side.right,
                          useCount: 0
                        ),
                        top: CubeFace(
                          letter: "S",
                          side: Side.top,
                          useCount: 0
                        ),
                        wasRemoved: false
                      ),
                    ]
                  ),
                ]
              ),
            ]
          ),
          gameContext: GameContext.solo,
          gameMode: GameMode.unlimited,
          gameStartTime: 2021-08-20T22:08:25Z,
          _language: Language.en,
          moves: Moves(
            rawValue: [
            ]
          ),
          secondsPlayed: 0
        )
      ),
      settings: SettingsState(
        alert: nil,
        buildNumber: nil,
        cubeShadowRadius: 50.0,
        developer: DeveloperSettings(
          currentBaseUrl: BaseUrl.production
        ),
        enableCubeShadow: true,
        enableNotifications: false,
        fullGameProduct: nil,
        fullGamePurchasedAt: nil,
        isPurchasing: false,
        isRestoring: false,
        sendDailyChallengeReminder: true,
        sendDailyChallengeSummary: true,
        showSceneStatistics: false,
        stats: StatsState(
          averageWordLength: nil,
          gamesPlayed: 0,
          highestScoringWord: nil,
          highScoreTimed: nil,
          highScoreUnlimited: nil,
          isAnimationReduced: false,
          isHapticsEnabled: true,
          longestWord: nil,
          route: nil,
          secondsPlayed: 0,
          wordsFound: 0
        ),
        userNotificationSettings: nil,
        userSettings: UserSettings(
          appIcon: nil,
          colorScheme: ColorScheme.system,
          enableGyroMotion: true,
          enableHaptics: true,
          enableReducedAnimation: false,
          musicVolume: 1.0,
          soundEffectsVolume: 1.0
        )
      ),
      turnBasedMatches: [
      ],
      weekInReview: nil
    )
  )

With the new improvements from Custom Dump this diff is now only 40 lines!

received action:
  AppAction.currentGame(
    GameFeatureAction.game(
      GameAction.tap(
        UIGestureRecognizer.State.began,
        1.2.2@left
      )
    )
  )
  AppState(
    game: GameState(
      activeGames: ActiveGamesState(…),
      alert: nil,
      bottomMenu: nil,
      cubes: […],
      cubeStartedShakingAt: nil,
      gameContext: GameContext.solo,
      gameCurrentTime: Date(2021-08-20T22:08:32.917Z),
      gameMode: GameMode.unlimited,
      gameOver: nil,
      gameStartTime: Date(2021-08-20T22:08:25.312Z),
      isDemo: false,
      isGameLoaded: true,
      isOnLowPowerMode: false,
      isPanning: false,
      isSettingsPresented: false,
      isTrayVisible: false,
      language: Language.en,
      moves: Moves(rawValue: []),
-     optimisticallySelectedFace: nil,
+     optimisticallySelectedFace: 1.2.2@left,
      secondsPlayed: 7,
      selectedWord: [
+       [0]: 1.2.2@left
      ],
      selectedWordIsValid: false,
      upgradeInterstitial: nil,
      wordSubmitButton: WordSubmitButtonState(…)
    ),
    onboarding: nil,
    home: HomeState(…)
  )

This makes it much easier to see exactly what changed when an action is sent.

Try it today

We’ve already started to get a lot of use out of Custom Dump, but we think there is so much more than can be done. Give it a spin today to develop new, creative debugging and testing tools for your team and others today!


Older blog posts

Wednesday Jul 14, 2021

Better Performance Bonanza

The past 3 weeks we've shipped updates to 3 of our libraries, focused on improving the performance of your Composable Architecture applications, and more!

Monday Jul 12, 2021

Open Sourcing Identified Collections

Today we are open sourcing Identified Collections, a library of data structures for working with collections of identifiable elements in a performant way.

Monday Jun 14, 2021

Announcing SwitchStore for the Composable Architecture

We are adding new tools for handling enum state in the Composable Architecture, with a focus on safety and performance.

Monday Jun 7, 2021

WWDC Sale

The year's biggest Apple event is here, and to celebrate we are offering a 25% discount off the first year for first-time subscribers.

Wednesday May 12, 2021

A Tour of isowords

This past month we released four completely free videos dedicated to diving into the real-world Swift code base of an iOS game we recently launched and open sourced: isowords.

Monday Mar 22, 2021

Better Testing Bonanza

We're open sourcing a library that makes it easier to be more exhaustive in writing tests.

Wednesday Mar 17, 2021

Open Sourcing isowords

We're open sourcing the entire code base to our newly released iOS word game, isowords!

Wednesday Mar 17, 2021

Announcing: isowords

We are excited to release isowords to the App Store, a new word search game for your phone. Download today!

Monday Mar 8, 2021

Composable Architecture Test Store Improvements

Composable Architecture 0.16.0 comes with significant improvements to its testing capabilities for tracking down effect-related failures.

Monday Feb 1, 2021

Composable Forms: Say "Bye" to Boilerplate!

Today we are releasing first-party support for concisely handling form data in the Composable Architecture.

Wednesday Dec 23, 2020

End-of-year sale: 25% off Point-Free

Through the new year, we're offering personal Point-Free subscriptions for 25% off the first year!

Wednesday Dec 23, 2020

2020 Year-in-review

The Composable Architecture, dependency management, parsers, Combine schedulers and more! Join us for a review of everything we accomplished in 2020!

Monday Dec 21, 2020

Open Sourcing Parsing

Today we are open sourcing Parsing, a library for turning nebulous data into well-structured data, with a focus on composition, performance, and generality.

Tuesday Jun 30, 2020

The Composable Architecture and SwiftUI Alerts

Today we are releasing a new version of the Composable Architecture with helpers that make working with SwiftUI alerts and action sheets a breeze.

Monday Jun 22, 2020

Core Motion support in the Composable Architecture

We are releasing our second mini-library for the Composable Architecture, which makes it easy to use Core Motion.

Monday Jun 15, 2020

Open Sourcing CombineSchedulers

Today we are open-sourcing CombineSchedulers, a library that introduces a few schedulers that makes working with Combine more testable and more versatile.

Wednesday May 27, 2020

Instrumenting features built in the Composable Architecture

Today we are releasing first-party support for instrumenting features built in the Composable Architecture.

Wednesday May 20, 2020

Core Location support in the Composable Architecture

We are releasing a mini-library that makes it easy to use Core Location inside the Composable Architecture.

Tuesday May 12, 2020

Regional Discounts

Regional discounts takes 50% off a monthly or yearly personal subscription to anyone whose credit card is issued from a particular list of countries.

Monday May 4, 2020

Composable Architecture, the library

Today we are releasing the Composable Architecture as an open-source library. It is a way to build applications in a consistent and understandable way, with composition, testing and ergonomics in mind.

Wednesday Mar 11, 2020

Announcing Episode Collections

After over two years and nearly 100 episodes we are finally launching episode collections on Point-Free!

Friday Feb 21, 2020

Share Point-Free with friends and save!

Today we're excited to announce support for referral bonuses! When friends and colleagues of yours subscribe to Point-Free with your referral link, both of you will get one month free.

Tuesday Feb 4, 2020

Open Sourcing Case Paths

Today we are open sourcing CasePaths, a library that introduces the power and ergonomics of key paths to enums!

Monday Dec 30, 2019

2019 Year-in-review

Random number generators, parsers, SwiftUI, composable architecture and more! Join us for a review of everything we accomplished in 2019!

Monday Dec 23, 2019

Snapshot Testing SwiftUI

Snapshot testing gives us broad test coverage on our SwiftUI views with very little up front work.

Wednesday Dec 18, 2019

Free Video: Testing SwiftUI

A free video exploring how to test SwiftUI.

Wednesday Nov 20, 2019

A Crash Course in Combine

Two free videos exploring Apple's new Combine framework, its core components, and how to integrate it in your code.

Thursday Nov 7, 2019

Higher-Order Snapshot Testing

How to enrich snapshot testing strategies with additional behavior using higher-order constructions.

Tuesday Jul 30, 2019

SwiftUI and State Management Corrections

Xcode 11 beta 5 has brought lots of changes to SwiftUI, and we'd like to take a moment to provide corrections to our episodes based on these changes.

Thursday May 9, 2019

Enterprise Subscriptions

Point-Free now supports enterprise subscriptions, making it easier than ever to manage a team subscription for larger organizations! For a fixed yearly rate, everyone with an email from your company's domain will get instant access to everything Point-Free has to offer. Contact us for more info!

Monday Apr 29, 2019

Open Sourcing Enum Properties

We wanted to make Swift enum data access as ergonomic as struct data access, so today we are open sourcing a code generation tool to do just that!

Monday Mar 18, 2019

Open Sourcing Gen

Today we are open sourcing Gen: a lightweight wrapper around Swift's randomness API's that makes randomness more composable, transformable and controllable!

Tuesday Jan 8, 2019

Announcing swift-html 0.2.0

Announcing swift-html 0.2.0: support for CocoaPods, Carthage, SnapshotTesting, and more!

Wednesday Dec 19, 2018

2018 Year-in-Review

41 episodes, 19 hours of video, 25 blog posts, 8 open source libraries, 3.8K stars, 36K visitors, and we’re just getting started?

Monday Dec 3, 2018

SnapshotTesting 1.0: Delightful Swift snapshot testing

Today we are open sourcing SnapshotTesting 1.0: a modern, composable snapshot testing library built entirely in Swift!

Monday Oct 29, 2018

Some news about contramap

We've seen that contramap is a powerful operation, but the name isn't fantastic. We propose a much more intuitive name for this operation, and in doing so make our code much easier to read.

Tuesday Oct 9, 2018

How to Control the World

APIs that interact with the outside world are unpredictable and make it difficult to test and simulate code paths in our apps. Existing solutions to this problem are verbose and complicated, so let's explore a simpler solution by embracing singletons and global mutation, and rejecting protocol-oriented programming and dependency injection.

Monday Oct 8, 2018

Watch episodes in your favorite podcast app!

Follow along with the newest Point-Free episodes using your favorite podcast app. We now support podcast-friendly RSS feeds for viewing all of our videos.

Thursday Sep 20, 2018

Random Zalgo Generator

Let's create a random Zalgo text generator using the simple Gen type we defined in this week's episode!

Thursday Sep 13, 2018

Type-safe HTML with Kitura

Today we're releasing a Kitura plug-in for rendering type-safe HTML. It provides a Swift compile-time API to HTML that prevents many of the runtime errors and vulnerabilities of traditional templated HTML rendering.

Thursday Sep 13, 2018

Type-safe HTML with Vapor

Today we're releasing a Vapor plug-in for rendering type-safe HTML. It provides a Swift compile-time API to HTML that prevents many of the runtime errors and vulnerabilities of traditional templated HTML rendering.

Wednesday Sep 12, 2018

Open sourcing swift-html: A Type-Safe Alternative to Templating Languages in Swift

Today we are open sourcing a new library for building HTML documents in Swift. It's extensible, transformable, type-safe, and provides many benefits over templating languages.

Friday Aug 17, 2018

Overture 0.3.0: Now with Zip

Today we are releasing Overture 0.3.0 with a bunch of useful zip functions.

Friday Aug 17, 2018

Open Sourcing Validated

Today we are open sourcing Validated, a tiny functional Swift library for handling multiple errors: functionality that you don't get from throwing functions and the Result type.

Thursday Aug 16, 2018

Solutions to Exercises: Zip Part 3

Today we solve the exercises to the third and final part of our introductory series on zip.

Wednesday Aug 15, 2018

Solutions to Exercises: Zip Part 2

Today we solve the exercises to the second part of our introductory series on zip.

Tuesday Aug 14, 2018

Solutions to Exercises: Zip Part 1

Today we solve the exercises to the first part of our introductory series on zip.

Monday Aug 6, 2018

Announcing Student Discounts

Get 50% off your Point-Free subscription with proof of enrollment at a university or coding school.

Monday Jul 30, 2018

Celebrating 6 Months

This week marks 6 months since our launch, and we’re making one of our most popular episodes free to the public!

Monday Jul 2, 2018

Conditional Coding

What happens when we combine Swift's conditional conformance with codability?

Monday Jun 25, 2018

Open Sourcing NonEmpty

Today we are open sourcing NonEmpty, a Swift library for modeling non-empty collection types. This small library can help make your code safer and more expressive with very little work.

Monday Jun 18, 2018

Tagged Seconds and Milliseconds

Let's create a type-safe interface for dealing with seconds and milliseconds in our programs. We'll use the `Tagged` type, which allows us to construct all new types in a lightweight way.

Wednesday May 30, 2018

Styling with Functions: Free for Everyone!

We are making one of our early episodes, “UIKit Styling with Functions”, free to everyone today! It’s a seminal episode that sets the foundation for some later work in the Point-Free series.

Tuesday May 15, 2018

Overture: Now with Functional Setters

Announcing Overture 0.2.0! This release is all about setters: functions that allow us to build complex transformations out of smaller units.

Monday May 7, 2018

Solutions to Exercises: Contravariance

This week we solve the exercises from our episode on contravariance, because there were _a lot_ of them!

Monday Apr 23, 2018

Case Study: Algebraic Data Types

Let’s look at a real world use for algebraic data types. We will refactor a data type that is used in the code on this very site so that the invalid states are unrepresentable by the compiler.

Monday Apr 23, 2018

Announcing Point-Free Pointers!

Today we are excited to announcement launch of Point-Free Pointers, a blog to supplement our video series for all the content we couldn’t fit in. Expect to find regularly postings here that dive even deeper into functional programming, showing real world use cases and more!