Skip to content

Instantly share code, notes, and snippets.

@atetlaw
Last active September 3, 2016 17:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atetlaw/a8ae93cb411f6dae107ce579a00229bd to your computer and use it in GitHub Desktop.
Save atetlaw/a8ae93cb411f6dae107ce579a00229bd to your computer and use it in GitHub Desktop.
This is a mad attempt to create collection of protocols and extensions that you can use to make your namespaced constants output their fully qualified names as strings, no matter how long the namespace. It makes use of associated types to link the nested constant elements in reverse.
public protocol ScopeNamed {
static func scope() -> String
}
/* Named represents the basic element, something that can output its name, but also has a parent scope */
public protocol Named {
associatedtype ParentScope: ScopeNamed
}
extension Named {
public static func localName() -> String {
return "\(self)"
}
public static func fullName() -> String {
return "\(ParentScope.scope())\(ParentScope.separator())\(self)"
}
public func localName() -> String {
return "\(self)"
}
public func fullName() -> String {
return "\(ParentScope.scope())\(ParentScope.separator())\(self)"
}
}
extension ScopeNamed {
public static func separator() -> String {
return "."
}
}
/*
Scoped is implemented by any constant element within the namespace.
Each "scoped" element also has a parent, this allows recursive access to all the elements in the namespace to
output their names.
*/
public protocol Scoped: Named, ScopeNamed {
associatedtype ParentScope: ScopeNamed
}
extension Scoped {
public static func scope() -> String {
return "\(ScopeName<ParentScope>().localName())\(separator())\(localName())"
}
public func scope() -> String {
return "\(ScopeName<ParentScope>().localName())\(ParentScope.separator())\(localName())"
}
}
/* This struct allows a kind of lookahead as the "scope()" method winds up the namespace chain gathering names */
private struct ScopeName<S: ScopeNamed>: Named {
typealias ParentScope = S
func localName() -> String {
return "\(ParentScope.scope())"
}
}
/* RootScope represents the root of the namespace */
public protocol RootScope :ScopeNamed {}
extension RootScope {
public static func scope() -> String {
return "\(self)"
}
public func scope() -> String {
return "\(self)"
}
}
/*:
# Example 1: app metrics
In an app you often want to create namespaced event names to send to your ananlytics provider.
This means you can record events using constants (avoiding typos, adding compile-time checking, and auto-completion),
but also output the fully-qualified name as a string.
*/
struct Event<Category: Scoped>: CustomStringConvertible {
var value: String
public var description: String {
return track()
}
init(_ string: String) {
self.value = string
}
func track() -> String {
return "\(Category.scope())\(Category.separator())\(value)"
}
}
enum metrics: RootScope {
enum home: Scoped {
typealias ParentScope = metrics
static let clicked_about = Event<home>(events.clicked_about.rawValue)
static let clicked_news = Event<home>(events.clicked_news.rawValue)
static let clicked_products = Event<home>(events.clicked_products.rawValue)
static let clicked_schedule = Event<home>(events.clicked_schedule.rawValue)
enum events: String {
case clicked_about, clicked_news, clicked_products, clicked_schedule
}
}
enum about: Scoped {
typealias ParentScope = metrics
static let clicked_contact = Event<about>(events.clicked_contact.rawValue)
enum events: String {
case clicked_contact
}
enum contact: Scoped {
typealias ParentScope = about
static let clicked_submit = Event<contact>(events.clicked_submit.rawValue)
static let clicked_cancel = Event<contact>(events.clicked_cancel.rawValue)
enum events: String {
case clicked_submit, clicked_cancel
}
}
}
}
/*:
## Here's the usage and output:
metrics.home.clicked_news.track() // "metrics.home.clicked_news"
metrics.home.clicked_about.track() // "metrics.home.clicked_about"
metrics.about.clicked_contact.track() // "metrics.about.clicked_contact"
metrics.about.contact.clicked_submit.track() // "metrics.about.contact.clicked_submit"
"\(metrics.home.clicked_news)" // "metrics.home.clicked_news"
I've used enums in the exampe but they could also be structs.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment