Search code examples
swiftnamed-parameters

Swift: How to resolve subscript ambiguity. Named subscripts?


I've implemented an ordered dictionary, which has two subscripts. The first is for the index (always of type Int) and the second is for the key of variing type keyType.

This all works fine until keyType is Int, because now the subscripts are obviously ambiguous.

Ambiguous use of 'subscript'

So to resolve to ambiguity, I tried to simply add a name tag key: for the value, just like normal function parameters have them, but this resulted in key: keyType being seen as my parameter type, which resulted in this error:

Cannot subscript a value of type 'OrderedDictionary' with an index of type '(key: Int)'

The error messages are very clear to me, yet I don't know how to resolve the ambiuity. For now I am using a function insert(for:value:).

Here is the OrderedDictionary class:

import Foundation

struct OrderedDictionary<keyType: Hashable, valueType>: Sequence {
    typealias Element = (keyType, valueType)

    let comparator: ((Element, Element) -> (Bool))?
    var keys = [keyType]()
    var values = [keyType:valueType]()
    var count: Int {
        get {
            return keys.count
        }
    }

    public struct Iterator: IteratorProtocol {
        let parent: OrderedDictionary
        var position: Int = 0

        init(parent: OrderedDictionary) {
            self.parent = parent
        }

        mutating func next() -> (keyType, valueType)? {
            if position == parent.keys.count {
                return nil
            }
            let entry = parent.getEntry(at: position)
            position += 1
            return entry
        }
    }

    init() {
        comparator = nil
    }

    init(comparator:  ((Element, Element) -> (Bool))?) {
        self.comparator = comparator
    }

    subscript(index: Int) -> valueType? {
        get {
            return self.values[self.keys[index]]
        }
        set(newValue) {
            let key = self.keys[index]
            if newValue == nil {
                self.keys.remove(at: index)
                self.values.removeValue(forKey: key)
            } else {
                self.values[key] = newValue
            }
        }
    }

    subscript(key: keyType) -> valueType? {
        get {
            return self.values[key]
        }
        set(newValue) {
            if newValue == nil {
                for i in (0..<self.keys.count) {
                    if self.keys[i] == key {
                        self.keys.remove(at: i)
                        break
                    }
                }
                self.values.removeValue(forKey: key)
            } else {
                insert(for: key, value: newValue!)
            }
        }
    }

    mutating func insert(for key: keyType, value: valueType) {
        let oldValue = self.values.updateValue(value, forKey: key)
        if oldValue == nil {
            if comparator != nil {
                for i in (0..<self.keys.count) {
                    if comparator!((key, value), getEntry(at: i)) {
                        self.keys.insert(key, at: i)
                        return
                    }
                }
            }
            //First key, largest key or insertion order
            self.keys.append(key)
        }
    }

    func getEntry(at index: Int) -> Element {
        let key = self.keys[index]
        return (key, self.values[key]!)
    }

    func makeIterator() -> OrderedDictionary<keyType, valueType>.Iterator {
        return Iterator(parent: self)
    }

    mutating func removeAll() {
        self.keys.removeAll()
        self.values.removeAll()
    }
}

And here the some examples in Swift:

//Note: Index type 'Int' same as key type 'Int'
var dict = OrderedDictionary<Int, String>()

// Ambiguous
dict[0] = "Hello"

// Named (not valid)
dict[key: 10] = "World"

// Workaround
dict.insert(for: 20, value: "!")

How do I resolve this ambiguity? Is this even possible in Swift?


Solution

  • Unlike all other functions in Swift, subscript parameter names are not externalized by default. (I regard this inconsistency as a bug in the language.)

    So, where you have

    subscript(key:
    

    ...you need to say

    subscript(key key:
    

    ...in order to expose the key label to the caller.


    Actually, it sounds to me like what you want is three subscripts:

    subscript(index: Int)
    subscript(key: keyType)
    subscript(key key: keyType)
    

    That way you can say key in the call only just in case ambiguity would result if you didn't.