Last active
January 21, 2016 08:55
-
-
Save erikrozendaal/c32863d399e4597ba0c7 to your computer and use it in GitHub Desktop.
Swift helpers voor minimal boilerplace "new type" support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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