Can we make this type into a monoid or a monad?

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.

A monoid is a type that has a combining operation and an identity element for that operation. I don't think that applies here.

A monad is a type that allows you to chain values that wrap some other value. (They're also notoriously hard to explain, so I'm going to go with with that for now.) That one might apply, but I'm not sure.

What are you actually trying to do? The point of these kinds of abstractions is to identify properties and reusable operations that apply to your type. How does that relate to your goals here?

Well it all started that I needed a type that defines the uniqueness of the value it holds by a Key like type. The naive implementation has a concrete key type called SerialNumber and it has a single mapValue function that transforms the current value into something else by keeping the key.

Then a good year later I now have special cases where the Key type must be different than SerialNumber which let me creating a new public struct Unique<Key, Value> where Key: Hashable { ... } but not in the way I posted above. That type also had a single map function for transforming the value.

Yesterday I bumped accidentally myself into a few bugs related to elements in a Set where I had 'duplicate' elements, at least so I assumed.

struct S: Hashable {
  let id: Int
  let something: String

  func hash(into hasher: inout Hasher) {
    hasher.combine(id)
  }
}

let a = S(id: 0, something: "a")
let b = S(id: 0, something: "b")
a != b
a.hashValue == b.hashValue

var set = Set<S>()
set.insert(a)
set.insert(b)
set.count == 2 // but I assumed it will be 1 because of the hash collision

My assumption was basically wrong, because I forgot that it's really about equality here and the hash value is only used for fast comparison and stuff like that.

That made me generalize the Unique type into the form from the original post. Now I'm just curious what useful API I could extend it with, other than map (which is not included above).


With Unique I can solve the above issue easily:

struct S: Hashable, Uniquified {
  let id: Int
  let something: String
}

let a = S(id: 0, something: "a")
let b = S(id: 0, something: "b")

var set = Set<S.UniquifiedBy<Int>>()
set.insert(a.uniquify(using: \.id))
set.insert(b.uniquify(using: \.id))
set.count == 1 // great