Permalink
Find file
158 lines (125 sloc) 4.91 KB

Add AnyHashable to the standard library

Introduction

We propose to add a type-erased AnyHashable container to the standard library.

The implementation of SE-0116 "Import Objective-C id as Swift Any type" requires a type-erased container for hashable values. From SE-0116:

We need a type-erased container to represent a heterogeneous hashable type that is itself Hashable, for use as the upper-bound type of heterogeneous Dictionarys and Sets.

Swift-evolution thread: Add AnyHashable to the standard library.

Motivation

Currently the Objective-C type NSDictionary * is imported as [NSObject : AnyObject]. We used NSObject as the key type because it is the closest type (in spirit) to AnyObject that also conforms to Hashable. The aim of SE-0116 is to eliminate AnyObject from imported APIs, replacing it with Any. To import unannotated NSDictionaries we need an Any-like type that conforms to Hashable. Thus, unannotated NSDictionaries will be imported as [AnyHashable : Any].

For additional motivation and discussion of API importing and bridging, see SE-0116.

Detailed design

We are adding the AnyHashable type:

/// A type-erased hashable value.
///
/// Forwards equality comparisons and hashing operations to an
/// underlying hashable value, hiding its specific type.
///
/// You can store mixed-type keys in `Dictionary` and other
/// collections that require `Hashable` by wrapping mixed-type keys in
/// `AnyHashable` instances:
///
///     let descriptions: [AnyHashable : Any] = [
///         AnyHashable("😄"): "emoji",
///         AnyHashable(42): "an Int",
///         AnyHashable(Int8(43)): "an Int8",
///         AnyHashable(Set(["a", "b"])): "a set of strings"
///     ]
///     print(descriptions[AnyHashable(42)]!)      // prints "an Int"
///     print(descriptions[AnyHashable(43)])       // prints "nil"
///     print(descriptions[AnyHashable(Int8(43))]!) // prints "an Int8"
///     print(descriptions[AnyHashable(Set(["a", "b"]))]!) // prints "a set of strings"
public struct AnyHashable {
  /// Creates an opaque hashable value that wraps `base`.
  ///
  /// Example:
  ///
  ///     let x = AnyHashable(Int(42))
  ///     let y = AnyHashable(UInt8(42))
  ///
  ///     print(x == y) // Prints "false" because `Int` and `UInt8`
  ///                   // are different types.
  ///
  ///     print(x == AnyHashable(Int(42))) // Prints "true".
  public init<H : Hashable>(_ base: H)

  /// The value wrapped in this `AnyHashable` instance.
  ///
  ///     let anyMessage = AnyHashable("Hello")
  ///     let unwrappedMessage: Any = anyMessage.base
  ///     print(unwrappedMessage) // prints "hello"
  public var base: Any
}

extension AnyHashable : Equatable, Hashable {
  public static func == (lhs: AnyHashable, rhs: AnyHashable) -> Bool
  public var hashValue: Int {
}

We are adding convenience APIs to Set<AnyHashable> that allow using existing Set APIs with concrete values that conform to Hashable. For example:

func contains42(_ data: Set<AnyHashable>) -> Bool {
  // Works, but is too verbose:
  // return data.contains(AnyHashable(42))

  return data.contains(42) // Convenience API.
}

Convenience APIs for Set<AnyHashable>:

extension Set where Element == AnyHashable {
  public func contains<ConcreteElement : Hashable>(
    _ member: ConcreteElement
  ) -> Bool

  public func index<ConcreteElement : Hashable>(
    of member: ConcreteElement
  ) -> SetIndex<Element>?

  mutating func insert<ConcreteElement : Hashable>(
    _ newMember: ConcreteElement
  ) -> (inserted: Bool, memberAfterInsert: ConcreteElement)

  @discardableResult
  mutating func update<ConcreteElement : Hashable>(
    with newMember: ConcreteElement
  ) -> ConcreteElement?

  @discardableResult
  mutating func remove<ConcreteElement : Hashable>(
    _ member: ConcreteElement
  ) -> ConcreteElement?
}

Convenience APIs for Dictionary<AnyHashable, *>:

extension Dictionary where Key == AnyHashable {
  public func index<ConcreteKey : Hashable>(forKey key: ConcreteKey)
    -> DictionaryIndex<Key, Value>?

  public subscript(_ key: _Hashable) -> Value? { get set }

  @discardableResult
  public mutating func updateValue<ConcreteKey : Hashable>(
    _ value: Value, forKey key: ConcreteKey
  ) -> Value?

  @discardableResult
  public mutating func removeValue<ConcreteKey : Hashable>(
    forKey key: ConcreteKey
  ) -> Value?
}

Impact on existing code

AnyHashable itself is additive. Source-breaking changes are discussed in SE-0116.