Swift Needs a Scope Keyword

Swift’s namespacing is great. It’s quite common now to see nested types like this:

class MyViewController: UIViewController {
  private enum State {
    case initialized
    case refreshing(previousModel: Model)
    case success(currentModel: Model)
    case error(Error)
  }
  private var state: State = .initialized {
    didSet { stateChanged(from: oldValue, to: state) }
  }
}

There’s no need for any class outside of MyViewController to access the State enum. Nesting it not only makes the intended usage obvious, it also lets you trim the type name down to a single word by obviating the need for a prefixed name like MyViewControllerState. Other classes are then free to nest their own State enums without worrying about name collisions.

Swift’s namespacing rules also allow you to group together related functions or constants that would otherwise be scoped at the module level:

struct Transformations {
  static func transform(_ foo: Foo) -> Bar {...}
  static func transform(_ bar: Bar) -> Foo {...}
}

struct Colors {
  static let background = UIColor.white
  static let bodyText = UIColor.black
}

Callers can then access a function like transform() without having to access a free function:

let b = Transformations.transform(f)

Please note this does not require the developer to initialize a Transformations instance. It’s hard to tell from my contrived example, but in practice it’s common to find types that would be nonsensical as instances but are nonetheless useful as scope providers.

But here’s the problem: how do you make it obvious to other developers that Transformations isn’t supposed to be initialized? One option: make the init method private:

struct Transformations {
  static func transform(_ foo: Foo) -> Bar {...}
  static func transform(_ bar: Bar) -> Foo {...}
  private init() {}
}

But that solution introduces more problems. First, it’s still not readily apparent that Transformations exists solely to provide a namespace. Second, this API pattern requires you to remember to make the init method private for every such type you ever create, which is a hassle. This is why the de rigueur solution right now is to use a caseless enum:

enum Transformations {
  static func transform(_ foo: Foo) -> Bar {...}
  static func transform(_ bar: Bar) -> Foo {...}
}

But this too is confusing because it’s not obvious that Transformations is meant to provide a namespace. It’s not really an enum. This is especially problematic in real-world examples where the functions and members of such a type are lengthy, making it impossible to tell at a glance whether there are any case declarations hidden somewhere in the file. It also does not prevent other developers from misunderstanding your intent and adding cases to the enum in the future.

I propose that Swift introduce a new scope keyword to address this common use case. My simple example might then look like this:

scope Transformations {
  static func transform(_ foo: Foo) -> Bar {...}
  static func transform(_ bar: Bar) -> Foo {...}
}

A scope can be declared the same way as structs, enums, and classes:

scope TYPENAME {
  // body
}

A scope can be declared either at a module level or nested within any type that supports nested types, including another scope:

scope OuterTurtle {
  scope MiddleTurtle {
    scope InnerTurtle {
    }
  }
}

A scope cannot be initialized, therefore a scope cannot have instance-level properties or methods. All methods and properties of a scope must be static. However, as a convenience the static keyword can be omitted since this is always implied:

scope Endpoints {
  scope Users {
    func getUser(withId id: String) -> Endpoint {...}
    func followUser(withId id: String) -> Endpoint {...}
  }
}

Scopes support the same access levels as other Swift types:

public scope Foo {
  public let qux = Bar.baz
  private scope Bar {
    let baz = "Baz"
  }
}

If you think this would be useful, please get in touch with me on Twitter.

|  17 Mar 2017