Here is a type I created to uniquify types by a key that is either distinct from the value type itself or extract from it.
public struct Unique<Key, Value>: Hashable where Key: Hashable {
private enum _KeyVariant {
case key(Key)
case keyPath(KeyPath<Value, Key>)
case closure((Value) -> Key)
func key(for value: Value) -> Key {
switch self {
case .key(let key):
return key
case .keyPath(let keyPath):
return value[keyPath: keyPath]
case .closure(let closure):
return closure(value)
}
}
}
private let _keyVariant: _KeyVariant
private var _value: Value
private init(_keyVariant: _KeyVariant, value: Value) {
self._keyVariant = _keyVariant
self._value = value
}
public init(key: Key, value: Value) {
self.init(_keyVariant: .key(key), value: value)
}
public init(keyPath: KeyPath<Value, Key>, value: Value) {
self.init(_keyVariant: .keyPath(keyPath), value: value)
}
public init(closure: @escaping (Value) -> Key, value: Value) {
self.init(_keyVariant: .closure(closure), value: value)
}
public var key: Key {
return _keyVariant.key(for: _value)
}
public var value: Value {
get { return _value }
set {
precondition(_hasIdenticalKey(with: newValue))
_value = newValue
}
}
public static func == (lhs: Unique, rhs: Unique) -> Bool {
return lhs.key == rhs.key
}
public func hash(into hasher: inout Hasher) {
hasher.combine(key)
}
}
extension Unique {
private func _hasIdenticalKey(with other: Value) -> Bool {
return _keyVariant.key(for: _value) == _keyVariant.key(for: other)
}
}
public protocol Uniquifiable {
typealias UniquifiedBy<Key: Hashable> = Unique<Key, Self>
func uniquify<Key: Hashable>(using key: Key) -> UniquifiedBy<Key>
func uniquify<Key: Hashable>(
using keyPath: KeyPath<Self, Key>
) -> UniquifiedBy<Key>
func uniquify<Key: Hashable>(
using closure: @escaping (Self) -> Key
) -> UniquifiedBy<Key>
}
extension Uniquifiable {
public func uniquify<Key: Hashable>(using key: Key) -> UniquifiedBy<Key> {
return UniquifiedBy(key: key, value: self)
}
public func uniquify<Key: Hashable>(
using keyPath: KeyPath<Self, Key>
) -> UniquifiedBy<Key> {
return UniquifiedBy(keyPath: keyPath, value: self)
}
public func uniquify<Key: Hashable>(
using closure: @escaping (Self) -> Key
) -> UniquifiedBy<Key> {
return UniquifiedBy(closure: closure, value: self)
}
}
extension Uniquifiable where Self: Hashable {
@available(swift, introduced: 5)
public func uniquified() -> UniquifiedBy<Self> {
return UniquifiedBy(keyPath: \.self, value: self)
}
}
Version with further updates will be available here:
Can you think of ways to make this type a monoid or a monad? Keep in mind that there is at least one condition that must be met in order to preserve the uniquence of an instantiated value. That is, if you mutate value
it should resolve into the same key, or in other words if you had an in-plcae mutable set that contains Unique
values, it should not cause any re-hashing if you'd mutate an existing value.
This thread is more an exercise discussion rather than an urgent problem that needs to be solved.