Skip to content

Instantly share code, notes, and snippets.

@MrAlek
Last active November 25, 2017 17:18
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 MrAlek/5c47645558a0dc2883dd1619dc523d3c to your computer and use it in GitHub Desktop.
Save MrAlek/5c47645558a0dc2883dd1619dc523d3c to your computer and use it in GitHub Desktop.
import Foundation
import UIKit
enum SafeRawRepresentable<Wrapped: RawRepresentable>: RawRepresentable {
case known(Wrapped)
case unknown(Wrapped.RawValue)
var rawValue: Wrapped.RawValue {
switch self {
case .known(let wrapped):
return wrapped.rawValue
case .unknown(let rawValue):
return rawValue
}
}
init(rawValue: Wrapped.RawValue) {
if let wrapped = Wrapped(rawValue: rawValue) {
self = .known(wrapped)
} else {
self = .unknown(rawValue)
}
}
}
extension SafeRawRepresentable {
var value: Wrapped? {
switch self {
case .known(let wrapped):
return wrapped
case .unknown(_):
return nil
}
}
func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U? {
return try value.flatMap(transform)
}
}
// Example struct
struct Car {
enum Brand: String {
case volvo, bmw, koenigsegg
}
let registrationNumber: String
let brand: SafeRawRepresentable<Brand>
init?(dictionary: [String: Any]) {
guard
let registrationNumber = dictionary["registrationNumber"] as? String,
let rawBrand = dictionary["brand"] as? String
else { return nil }
self.registrationNumber = registrationNumber
self.brand = .init(rawValue: rawBrand)
}
}
// Example of computed property based on type safe enum
extension Car.Brand {
var isSwedish: Bool {
switch self {
case .volvo, .koenigsegg:
return true
case .bmw:
return false
}
}
}
// Example use (try in playground)
let knownBrandCar = Car(dictionary: ["registrationNumber": "ABC-123", "brand": "volvo"])!
let unknownBrandCar = Car(dictionary: ["registrationNumber": "CBA-321", "brand": "toyota"])!
[knownBrandCar, unknownBrandCar].forEach { car in
print("Car: \(car.registrationNumber)")
print("Made in Sweden 🇸🇪: " + (car.brand.flatMap { $0.isSwedish ? "Yep" : "Nope" } ?? "🤷"))
print("")
}
// Car: ABC-123
// Made in Sweden 🇸🇪: Yep
//
// Car: CBA-321
// Made in Sweden 🇸🇪: 🤷
//
if let brand = knownBrandCar.brand.value {
// Do things with car brand
}
// You can also do unwrapping
let showSwedishFlag = unknownBrandCar.brand.value?.isSwedish ?? false
class CarBrandView: UIView {
init(brand: Car.Brand) {
super.init(frame: .zero)
// Customize view depending on car brand
}
required init?(coder aDecoder: NSCoder) { fatalError("Not implemented") }
}
// And pass type safe values to functions
let view = unknownBrandCar.brand.flatMap(CarBrandView.init) as? UIView ?? {
// Fallback view
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 400, height: 44))
label.text = "We have no information about this brand"
return label
}()
@martijnwalraven
Copy link

Sorry for only responding now. This looks like a good alternative to the .unknown case to me, so curious what @JustinDSN thinks.

It'll require some getting used to, but the semantics seem to make sense. We may want to add more convenience methods to this where possible, including Equatable conformance.

@MrAlek
Copy link
Author

MrAlek commented Nov 25, 2017

You get == for free by conforming to RawRepresentable but Equatable conformance for generic structs isn't currently doable I believe. Array isn't Equatable either. I'm having a hack night on Wednesday, could work on a PR after the group call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment