Reducing the boilerplate for my ID types


(Ray Fix) #1

Hello,

I have some ID types that are simple ints coming back from a database. I wanted to improve type safety so I don’t accidentally assign product IDs to user IDs, etc. I want to be able to print it and use it as a dictionary key. So it is a trivial wrapper of Int. E.g.

struct CustomerID: Hashable, CustomStringConvertible {
    init(_ value: Int) { self.value = value }
    let value: Int
    var hashValue: Int { return value.hashValue }
    var description: String { return String(value) }
}

func ==(lhs: CustomerID, rhs: CustomerID) -> Bool {
    return lhs.value == rhs.value
}

While it isn’t too much code, it seems like a lot boilerplate. I tried to reduce the boilerplate with protocol extensions. After a little bit of compiler fighting I came up with:

public protocol IDType: Hashable, CustomStringConvertible {
    init(_ value: Int)
    var value: Int { get }
}

extension IDType {
    public var hashValue: Int { return value.hashValue }
    public var description: String { return String(value) }
}

Notes (1) compiler made me declare my protocol public since Hashable and CustomStringConvertible are public. (2) I could not implement init(_ value: Int) in the extension so I have to do that each time (3) Self requirement built into Hashable meant that I could not implement an == for all IDTypes. So the final product looks like the following. Given the additional complexity, doesn’t seem like it is worth it.

public struct ProductID: IDType {
    public init(_ value: Int) { self.value = value }
    public let value: Int
}

public func ==(lhs: ProductID, rhs: ProductID) -> Bool {
    return lhs.value == rhs.value
}

I wonder if the hive mind had any better ideas or different approaches for creating such types with minimal boilerplate. [Is there a possible new language feature coming that will make this more trivial?]

Ray


(Brent Royal-Gordon) #2

I have some ID types that are simple ints coming back from a database. I wanted to improve type safety so I don’t accidentally assign product IDs to user IDs, etc. I want to be able to print it and use it as a dictionary key. So it is a trivial wrapper of Int. E.g.

struct CustomerID: Hashable, CustomStringConvertible {
    init(_ value: Int) { self.value = value }
    let value: Int
    var hashValue: Int { return value.hashValue }
    var description: String { return String(value) }
}

func ==(lhs: CustomerID, rhs: CustomerID) -> Bool {
    return lhs.value == rhs.value
}

Rather than going the protocol route, how about a generic type?

    struct ID<IdentifiableType: Identifiable>: Hashable, CustomStringConvertible {
        init(_ value: Int) { self.value = value }
        let value: Int
        var hashValue: Int { return value.hashValue }
        var description: String { return String(value) }
    }

    func ==<T: Identifiable>(lhs: ID<T>, rhs: ID<T>) -> Bool {
        return lhs.value == rhs.value
    }

    protocol Identifiable {
        var id: ID<Self>? { get }
    }

    struct Customer: Identifiable {
        var id: ID<Customer>?
    }

···

--
Brent Royal-Gordon
Architechies


(Ray Fix) #3

Very cool! Thank you.

Notes to self:

ID uses the Identifiable model as sort of a phantom type. Given my current code base, it also convenient for me to do

typealias CustomerID = ID<Customer>

Being able to define new ID types in one line like this is exactly the solution I was looking for.

Ray

···

On May 17, 2016, at 7:24 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

I have some ID types that are simple ints coming back from a database. I wanted to improve type safety so I don’t accidentally assign product IDs to user IDs, etc. I want to be able to print it and use it as a dictionary key. So it is a trivial wrapper of Int. E.g.

struct CustomerID: Hashable, CustomStringConvertible {
   init(_ value: Int) { self.value = value }
   let value: Int
   var hashValue: Int { return value.hashValue }
   var description: String { return String(value) }
}

func ==(lhs: CustomerID, rhs: CustomerID) -> Bool {
   return lhs.value == rhs.value
}

Rather than going the protocol route, how about a generic type?

   struct ID<IdentifiableType: Identifiable>: Hashable, CustomStringConvertible {
       init(_ value: Int) { self.value = value }
       let value: Int
       var hashValue: Int { return value.hashValue }
       var description: String { return String(value) }
   }

   func ==<T: Identifiable>(lhs: ID<T>, rhs: ID<T>) -> Bool {
       return lhs.value == rhs.value
   }

   protocol Identifiable {
       var id: ID<Self>? { get }
   }

   struct Customer: Identifiable {
       var id: ID<Customer>?
   }


(Matthew Johnson) #4

I have some ID types that are simple ints coming back from a database. I wanted to improve type safety so I don’t accidentally assign product IDs to user IDs, etc. I want to be able to print it and use it as a dictionary key. So it is a trivial wrapper of Int. E.g.

struct CustomerID: Hashable, CustomStringConvertible {
   init(_ value: Int) { self.value = value }
   let value: Int
   var hashValue: Int { return value.hashValue }
   var description: String { return String(value) }
}

func ==(lhs: CustomerID, rhs: CustomerID) -> Bool {
   return lhs.value == rhs.value
}

Rather than going the protocol route, how about a generic type?

   struct ID<IdentifiableType: Identifiable>: Hashable, CustomStringConvertible {
       init(_ value: Int) { self.value = value }
       let value: Int
       var hashValue: Int { return value.hashValue }
       var description: String { return String(value) }
   }

   func ==<T: Identifiable>(lhs: ID<T>, rhs: ID<T>) -> Bool {
       return lhs.value == rhs.value
   }

   protocol Identifiable {
       var id: ID<Self>? { get }
   }

   struct Customer: Identifiable {
       var id: ID<Customer>?
   }

Nice!

···

On May 17, 2016, at 9:24 PM, Brent Royal-Gordon via swift-users <swift-users@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users