Can't implement a Dictionary extension initializer with a closure

Continuing the discussion from Tip: Doing more than two partitions for a Sequence (or Collection):

Instead of using the Sequence extension method given in the previous thread, I wanted to move it to a variant initializer on Dictionary.

extension Dictionary {
    public init<S: Sequence>(grouping values: __owned S, as type: Value.Type, by keyForValue: (Value.Element) throws -> Key) rethrows where Value: RangeReplaceableCollection, S.Element == Value.Element {
        self = try Dictionary(grouping: values, by: keyForValue).mapValues({ Value($0) })
    }
}

but kept getting an error on the predicate. I tried another approach:

extension Dictionary where Value: RangeReplaceableCollection {

    /// Creates a new dictionary whose keys are the groupings returned by the
    /// given closure and whose values are collections of the elements, using a
    /// given type, that returned each key.
    ///
    /// The collections in the "values" position of the new dictionary each
    /// contain at least one element, with the elements in the same order as the
    /// source sequence.
    ///
    /// The following example declares a string, then creates a dictionary from
    /// that string by grouping by a filtering predicate.
    ///
    ///     let vowels = Set<Character> = ["a", "e", "i", "o", "u"]
    ///     let swiftMessage = "Swift Dictionary"
    ///     let swiftMsgByType = Dictionary(grouping: swiftMessage, as: String.self, by: { vowels.contains($0) })
    ///     // [false: "Swft Dctnry", true: "iiioa"]
    ///
    /// The new `swiftMsgByType` dictionary has two entries, one grouping
    /// non-vowels (key `false`) and one group with vowels (key `true`).
    ///
    /// - Parameters:
    ///   - values: A sequence of values to group into a dictionary.
    ///   - type: A metatype specifier for the per-key collection type of
    ///     elements.
    ///   - keyForValue: A closure that returns a key for each element in
    ///     `values`.
    @inlinable
    public init<S: Sequence>(grouping values: S, as type: Value.Type, by keyForValue: (S.Element) throws -> Key) rethrows where S.Element == Value.Element {
        let arrayedDictionary = try Dictionary(grouping: values, by: keyForValue)
        self = arrayedDictionary.mapValues(Value.init)
    }
}

The error occurs on the first line, highlighting "keyForValue". The error is:

Cannot convert value of type '(Value.Element) throws -> Key' to expected argument type '(_) throws -> _'

The fix-it is a non-sensical

Insert ' as! (_) throws -> _'

(Using the fix gives an "Expected type" error.) The code seems like it should work, since Value.Element and S.Element must be the same. Is there a compiler bug in recognizing transitive closure signatures in initializers? From the other post, the code works just fine outside an initializer.

It's because Dictionary inside Dictionary extension is inferred to match the enclosing extension. This applies to all generics. You can explicitly specify the type parameters.

extension Dictionary {
    public init<S: Sequence>(grouping values: __owned S, as type: Value.Type, by keyForValue: (Value.Element) throws -> Key) rethrows where Value: RangeReplaceableCollection, S.Element == Value.Element {
        // [Key: [Value.Element]] will also do.
        self = try Dictionary<Key, [Value.Element]>(grouping: values, by: keyForValue).mapValues(Value.init)
    }
}
2 Likes

…and this “feature” is something that members of the core team have expressed interest in removing, such as in this thread.

Thank you.

The suggestion to add this initializer is at SR-11442 ("Add generalized variant of Dictionary.init(grouping: by:)")