Skip to content

Instantly share code, notes, and snippets.

@erikrozendaal
Last active January 21, 2016 08:55
Show Gist options
  • Save erikrozendaal/c32863d399e4597ba0c7 to your computer and use it in GitHub Desktop.
Save erikrozendaal/c32863d399e4597ba0c7 to your computer and use it in GitHub Desktop.
Swift helpers voor minimal boilerplace "new type" support
//
// Packages a value into a new, type-safe wrapper. The value can always be safely unwrapped.
//
public protocol NewType: CustomStringConvertible {
// The type of the wrapped value
typealias Unwrapped
// Accessor for the underlying value
var unwrapped: Unwrapped { get }
// Required constructor
init(unwrapped: Unwrapped)
}
public extension NewType {
// Construct a wrapped value without having to specify the external name.
public init(_ value: Unwrapped) {
self.init(unwrapped: value)
}
var description: String {
return "\(self.dynamicType)(\(self.unwrapped))"
}
}
// Conform to the equatable protocol iff the wrapped value is equatable
public func ==<T: NewType where T.Unwrapped: Equatable>(lhs: T, rhs: T) -> Bool {
return lhs.unwrapped == rhs.unwrapped
}
// Conform to the comparable protocol iff the wrapped value is comparable
public func < <T: NewType where T.Unwrapped: Comparable>(lhs: T, rhs: T) -> Bool {
return lhs.unwrapped < rhs.unwrapped
}
// Conform to the hashable protocol iff the wrapped value is hashable
public extension NewType where Unwrapped: Hashable {
var hashValue: Int {
return 31 &* self.unwrapped.hashValue
}
}
// Conform to the StringLiteralConvertible iff the wrapped value is StringLiteralConvertible, useful for tests?
public extension NewType where Unwrapped: StringLiteralConvertible {
init(unicodeScalarLiteral value: Unwrapped) {
self.init(value)
}
init(extendedGraphemeClusterLiteral value: Unwrapped) {
self.init(value)
}
init(stringLiteral value: Unwrapped) {
self.init(value)
}
}
import Foundation
import NewType
// Actually define new types with minimal boilerplate
struct ServiceID: NewType, Equatable, Hashable {
let unwrapped: String
}
// Instantiate a service ID value
let thermostat = ServiceID("T")
// New type with string literal support
struct RoomID: NewType, Equatable, Hashable, StringLiteralConvertible {
let unwrapped: String
}
// Use explicit initializer
let livingRoom = RoomID("w")
// Use the string literal conversion
let bedroom: RoomID = "s"
// prints 'RoomID(value: "w") == RoomID(value: "s") => false'
print("\(livingRoom) == \(bedroom) => \(livingRoom == bedroom)")
// Use StringLiteralConvertible for easy testing
print("w" == livingRoom)
// RoomID cannot be compared with ServiceID (compile error)
//print(livingRoom == thermostat)
// More examples
struct Room {
let id: RoomID
let description: String
}
struct Service {
let id: ServiceID
}
extension CollectionType {
func toDictionary<K, V>(@noescape transform: (element: Self.Generator.Element) -> (key: K, value: V)) -> [K : V] {
return self.reduce([:]) { (var dictionary, e) in
let (key, value) = transform(element: e)
dictionary[key] = value
return dictionary
}
}
}
// Because we use type-safe identifiers for different things (rooms,
// services), we can overload the subscription operation
class Home {
private let rooms: [RoomID : Room]
private let services: [ServiceID: Service]
init(rooms: [Room], services: [Service]) {
self.rooms = rooms.toDictionary { ($0.id, $0) }
self.services = services.toDictionary { ($0.id, $0) }
}
subscript(room: RoomID) -> Room? {
return rooms[room]
}
subscript(service: ServiceID) -> Service? {
return services[service]
}
}
let home = Home(
rooms: [
Room(id: livingRoom, description: "spacious living room"),
Room(id: bedroom, description: "cozy bedroom")
],
services: [
Service(id: thermostat)
]
)
let room = home[livingRoom]
print("\(home) has a living room: \(home[bedroom])")
// New type with restricted domain of underlying value
struct NonEmptyString: NewType, Equatable, Hashable {
let unwrapped: String
var description: String {
return unwrapped
}
init(unwrapped: String) {
self.init(unwrapped)!
}
init?(_ value: String) {
if value.isEmpty {
return nil
}
self.unwrapped = value
}
}
let nonEmpty: NonEmptyString? = NonEmptyString("")
print(nonEmpty)
// New type to normalize strings (remove leading and trailing
// whitespace, normalize all internal whitespace to a single space)
struct NormalizedString: NewType, Equatable, Hashable {
let unwrapped: String
var description: String {
return unwrapped
}
init(unwrapped: String) {
self.unwrapped = unwrapped
.stringByReplacingOccurrencesOfString("\\W+", withString: " ", options: NSStringCompareOptions.RegularExpressionSearch, range: nil)
.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
}
}
print(NormalizedString(" John Doe "))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment