[Proposal] mapValues


(Miguel Angel Quinones) #1

I'm +1 for adding mapValues. Very useful functionality and trivial to implement.

···

> I.e. I suggest to implement and mapKeys() also. It could be also useful in some situations.
`mapKeys` is much more dangerous, because you could end up mapping many values into a single key. You kind of need to combine the values somehow. Perhaps:

extension Dictionary {
func mapValues(_ valueTransform: @noescape Value throws ->OutValue) rethrows ->[Key: OutValue] { … }

func mapKeys(_ keyTransform: @noescape Key throws ->OutKey) rethrows ->[OutKey: [Value]] { … }

// Possibly flatMap variants, too?
}

extension Dictionary where Value: Sequence {
func reduceValues(_ initial: OutValue, combine: @noescape (OutValue, Value.Iterator.Element) throws ->OutValue) rethrows ->[Key: OutValue] {
return mapValues { $0.reduce(initial, combine: combine) }
}
}

Which you would end up using like this:

let wordFrequencies: [String: Int] = …
let firstLetterFrequencies: [Character: Int] = wordFrequencies.mapKeys { $0.characters.first! }.reduceValues(0, combine: +)

--
Brent Royal-Gordon
Architechies

--
Miguel Angel Quinones


(Ross O'Brien) #2

+1 on mapValues.

DictionaryLiteral already throws an exception if it includes duplicate
keys, so I'd expect mapKeys to throw an error if multiple source keys
mapped to the same destination key.

···

On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via swift-evolution <swift-evolution@swift.org> wrote:

I'm +1 for adding mapValues. Very useful functionality and trivial to
implement.

> > I.e. I suggest to implement and mapKeys() also. It could be also
useful in some situations.
> `mapKeys` is much more dangerous, because you could end up mapping many
values into a single key. You kind of need to combine the values somehow.
Perhaps:
>
> extension Dictionary {
> func mapValues(_ valueTransform: @noescape Value throws ->OutValue)
rethrows ->[Key: OutValue] { … }
>
> func mapKeys(_ keyTransform: @noescape Key throws ->OutKey) rethrows
->[OutKey: [Value]] { … }
>
> // Possibly flatMap variants, too?
> }
>
> extension Dictionary where Value: Sequence {
> func reduceValues(_ initial: OutValue, combine: @noescape (OutValue,
Value.Iterator.Element) throws ->OutValue) rethrows ->[Key: OutValue] {
> return mapValues { $0.reduce(initial, combine: combine) }
> }
> }
>
> Which you would end up using like this:
>
> let wordFrequencies: [String: Int] = …
> let firstLetterFrequencies: [Character: Int] = wordFrequencies.mapKeys {
$0.characters.first! }.reduceValues(0, combine: +)
>
> --
> Brent Royal-Gordon
> Architechies
>
>
>
>

--
Miguel Angel Quinones

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Vladimir) #3

As for mapKeys and many values into a single key. I believe we should have a choice - do we expect multiply values for the same key or not. Just like "+" and integer overflow : by default it raises the error, but if "&+" - we expect the overflow. I can imagine situations when it is ok for me to have different values for the same key(if I don't care which of values should be for that key in result dictionary).
So my proposal is some additional mapKey(allowMultiplyValues: true) {...} or in any other form/name.

···

On 13.04.2016 13:38, Ross O'Brien via swift-evolution wrote:

+1 on mapValues.

DictionaryLiteral already throws an exception if it includes duplicate
keys, so I'd expect mapKeys to throw an error if multiple source keys
mapped to the same destination key.

On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via swift-evolution > <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

    I'm +1 for adding mapValues. Very useful functionality and trivial to
    implement.

     > > I.e. I suggest to implement and mapKeys() also. It could be also
    useful in some situations.
    > `mapKeys` is much more dangerous, because you could end up mapping many values into a single key. You kind of need to combine the values somehow. Perhaps:
    >
    > extension Dictionary {
     > func mapValues__(_ valueTransform: @noescape Value throws
    ->OutValue) rethrows ->[Key: OutValue] { … }
     >
     > func mapKeys__(_ keyTransform: @noescape Key throws ->OutKey)
    rethrows ->[OutKey: [Value]] { … }
    >
    > // Possibly flatMap variants, too?
    > }
    >
    > extension Dictionary where Value: Sequence {
     > func reduceValues__(_ initial: OutValue, combine: @noescape
    (OutValue, Value.Iterator.Element) throws ->OutValue) rethrows ->[Key:
    OutValue] {
    > return mapValues { $0.reduce(initial, combine: combine) }
    > }
    >
    > Which you would end up using like this:
    >
    > let wordFrequencies: [String: Int] = …
    > let firstLetterFrequencies: [Character: Int] = wordFrequencies.mapKeys { $0.characters.first! }.reduceValues(0, combine: +)
    >
    > --
    > Brent Royal-Gordon
    > Architechies
    >
    >______

    --
    Miguel Angel Quinones

    _______________________________________________
    swift-evolution mailing list
    swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jacob Bandes-Storch) #4

To adhere to the API Design Guidelines, I think it should be named
"mappingValues", right?

···

On Wed, Apr 13, 2016 at 4:23 AM, Vladimir.S via swift-evolution < swift-evolution@swift.org> wrote:

As for mapKeys and many values into a single key. I believe we should have
a choice - do we expect multiply values for the same key or not. Just like
"+" and integer overflow : by default it raises the error, but if "&+" - we
expect the overflow. I can imagine situations when it is ok for me to have
different values for the same key(if I don't care which of values should be
for that key in result dictionary).
So my proposal is some additional mapKey(allowMultiplyValues: true) {...}
or in any other form/name.

On 13.04.2016 13:38, Ross O'Brien via swift-evolution wrote:

+1 on mapValues.

DictionaryLiteral already throws an exception if it includes duplicate
keys, so I'd expect mapKeys to throw an error if multiple source keys
mapped to the same destination key.

On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via >> swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

    I'm +1 for adding mapValues. Very useful functionality and trivial to
    implement.

     > > I.e. I suggest to implement and mapKeys() also. It could be also
    useful in some situations.
    > `mapKeys` is much more dangerous, because you could end up mapping
many values into a single key. You kind of need to combine the values
somehow. Perhaps:
    >
    > extension Dictionary {
     > func mapValues__(_ valueTransform: @noescape Value throws
    ->OutValue) rethrows ->[Key: OutValue] { … }
     >
     > func mapKeys__(_ keyTransform: @noescape Key throws ->OutKey)
    rethrows ->[OutKey: [Value]] { … }
    >
    > // Possibly flatMap variants, too?
    > }
    >
    > extension Dictionary where Value: Sequence {
     > func reduceValues__(_ initial: OutValue, combine: @noescape
    (OutValue, Value.Iterator.Element) throws ->OutValue) rethrows ->[Key:
    OutValue] {
    > return mapValues { $0.reduce(initial, combine: combine) }
    > }
    > }
    >
    > Which you would end up using like this:
    >
    > let wordFrequencies: [String: Int] = …
    > let firstLetterFrequencies: [Character: Int] =
wordFrequencies.mapKeys { $0.characters.first! }.reduceValues(0, combine: +)
    >
    > --
    > Brent Royal-Gordon
    > Architechies
    >
    >
    >
    >______

    --
    Miguel Angel Quinones

    _______________________________________________
    swift-evolution mailing list
    swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________

swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Nate Cook) #5

To adhere to the API Design Guidelines, I think it should be named "mappingValues", right?

As for mapKeys and many values into a single key. I believe we should have a choice - do we expect multiply values for the same key or not. Just like "+" and integer overflow : by default it raises the error, but if "&+" - we expect the overflow. I can imagine situations when it is ok for me to have different values for the same key(if I don't care which of values should be for that key in result dictionary).
So my proposal is some additional mapKey(allowMultiplyValues: true) {...} or in any other form/name.

There's a proposal (awaiting merging) to add Dictionary initializers and methods that work with key/value pairs. These provide different ways of dealing with the duplicate key issue after a call to the regular Collection.map method.
https://github.com/natecook1000/swift-evolution/blob/natecook-dictionary-merge/proposals/0000-add-sequence-based-init-and-merge-to-dictionary.md

I'd be interested in a `mapValues` or `transformValues` method that would modify values in place while leaving keys alone.

Another useful method (that could be used to build mapValues more efficiently) would be `Dictionary.updateValue(value: Value, at index: DictionaryIndex)`, so you could write:

var dict = ["a": 1, "b": 2, "c": 3]
if let i = dict.index(where: { $0.value == 3 }) {
    dict.updateValue(100, at: i)
}
// dict == ["a": 1, "b": 2, "c": 100]

-Nate

···

On Apr 13, 2016, at 12:02 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org> wrote:
On Wed, Apr 13, 2016 at 4:23 AM, Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 13.04.2016 13:38, Ross O'Brien via swift-evolution wrote:
+1 on mapValues.

DictionaryLiteral already throws an exception if it includes duplicate
keys, so I'd expect mapKeys to throw an error if multiple source keys
mapped to the same destination key.

On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via swift-evolution > <swift-evolution@swift.org <mailto:swift-evolution@swift.org> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> wrote:

    I'm +1 for adding mapValues. Very useful functionality and trivial to
    implement.

     > > I.e. I suggest to implement and mapKeys() also. It could be also
    useful in some situations.
    > `mapKeys` is much more dangerous, because you could end up mapping many values into a single key. You kind of need to combine the values somehow. Perhaps:
    >
    > extension Dictionary {
     > func mapValues__(_ valueTransform: @noescape Value throws
    ->OutValue) rethrows ->[Key: OutValue] { … }
     >
     > func mapKeys__(_ keyTransform: @noescape Key throws ->OutKey)
    rethrows ->[OutKey: [Value]] { … }
    >
    > // Possibly flatMap variants, too?
    > }
    >
    > extension Dictionary where Value: Sequence {
     > func reduceValues__(_ initial: OutValue, combine: @noescape
    (OutValue, Value.Iterator.Element) throws ->OutValue) rethrows ->[Key:
    OutValue] {
    > return mapValues { $0.reduce(initial, combine: combine) }
    > }
    > }
    >
    > Which you would end up using like this:
    >
    > let wordFrequencies: [String: Int] = …
    > let firstLetterFrequencies: [Character: Int] = wordFrequencies.mapKeys { $0.characters.first! }.reduceValues(0, combine: +)
    >
    > --
    > Brent Royal-Gordon
    > Architechies
    >
    >
    >
    >______

    --
    Miguel Angel Quinones

    _______________________________________________
    swift-evolution mailing list
    swift-evolution@swift.org <mailto:swift-evolution@swift.org> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>
    https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #6

    To adhere to the API Design Guidelines, I think it should be named
    "mappingValues", right?

    As for mapKeys and many values into a single key. I believe we should have a
        choice - do we expect multiply values for the same key or not. Just like
        "+" and integer overflow : by default it raises the error, but if "&+" -
        we expect the overflow. I can imagine situations when it is ok for me to
        have different values for the same key(if I don't care which of values
        should be for that key in result dictionary).
        So my proposal is some additional mapKey(allowMultiplyValues: true)
        {...} or in any other form/name.

There's a proposal (awaiting merging) to add Dictionary initializers and methods
that work with key/value pairs. These provide different ways of dealing with the
duplicate key issue after a call to the regular Collection.map method.

    https://github.com/natecook1000/swift-evolution/blob/natecook-dictionary-merge/proposals/0000-add-sequence-based-init-and-merge-to-dictionary.md

Ah, yes, that reminds me of your answer to the question about whether we
need a “uniquingKeys” label on that init(), which IMO is a good one: no,
we don't need it, because the one without the label traps on duplicate
keys.

I'd be interested in a `mapValues` or `transformValues` method that would modify
values in place while leaving keys alone.

Is that enough of an improvement over

   Dictionary(d.lazy.map { (k,v) in (k, transform(v)) })

(once we get that initializer) to make it worth expanding the Dictionary
API?

Another useful method (that could be used to build mapValues more efficiently)
would be `Dictionary.updateValue(value: Value, at index: DictionaryIndex)`, so
you could write:

var dict = ["a": 1, "b": 2, "c": 3]
if let i = dict.index(where: { $0.value == 3 }) {
dict.updateValue(100, at: i)
}
// dict == ["a": 1, "b": 2, "c": 100]

Indeed it would!

···

on Wed Apr 13 2016, Nate Cook <swift-evolution@swift.org> wrote:

    On Apr 13, 2016, at 12:02 PM, Jacob Bandes-Storch via swift-evolution > <swift-evolution@swift.org> wrote:
    On Wed, Apr 13, 2016 at 4:23 AM, Vladimir.S via swift-evolution > <swift-evolution@swift.org> wrote:

-Nate

        On 13.04.2016 13:38, Ross O'Brien via swift-evolution wrote:

        +1 on mapValues.

            DictionaryLiteral already throws an exception if it includes
            duplicate
            keys, so I'd expect mapKeys to throw an error if multiple source
            keys
            mapped to the same destination key.

            On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via > swift-evolution > <swift-evolution@swift.org > <mailto:swift-evolution@swift.org>> > wrote:

            I'm +1 for adding mapValues. Very useful functionality and trivial
            to
            implement.

            > > I.e. I suggest to implement and mapKeys() also. It could be also
            useful in some situations.
            > `mapKeys` is much more dangerous, because you could end up mapping
            many values into a single key. You kind of need to combine the
            values somehow. Perhaps:
            >
            > extension Dictionary {
            > func mapValues__(_ valueTransform: @noescape Value throws
            ->OutValue) rethrows ->[Key: OutValue] { … }
            >
            > func mapKeys__(_ keyTransform: @noescape Key throws ->OutKey)
            rethrows ->[OutKey: [Value]] { … }
            >
            > // Possibly flatMap variants, too?
            > }
            >
            > extension Dictionary where Value: Sequence {
            > func reduceValues__(_ initial: OutValue, combine: @noescape
            (OutValue, Value.Iterator.Element) throws ->OutValue) rethrows ->
            [Key:
            OutValue] {
            > return mapValues { $0.reduce(initial, combine: combine) }
            > }
            > }
            >
            > Which you would end up using like this:
            >
            > let wordFrequencies: [String: Int] = …
            > let firstLetterFrequencies: [Character: Int] =
            wordFrequencies.mapKeys { $0.characters.first! }.reduceValues(0,
            combine: +)
            >
            > --
            > Brent Royal-Gordon
            > Architechies
            >
            >
            >
            >______

            --
            Miguel Angel Quinones

            _______________________________________________
            swift-evolution mailing list
            swift-evolution@swift.org
            <mailto:swift-evolution@swift.org>
            https://lists.swift.org/mailman/listinfo/swift-evolution

            _______________________________________________
            swift-evolution mailing list
            swift-evolution@swift.org
            https://lists.swift.org/mailman/listinfo/swift-evolution

        _______________________________________________
        swift-evolution mailing list
        swift-evolution@swift.org
        https://lists.swift.org/mailman/listinfo/swift-evolution

    _______________________________________________
    swift-evolution mailing list
    swift-evolution@swift.org
    https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave


(Andrew Bennett) #7

I don't think I've ever called map on a dictionary in production code, only
a mapValue function like this thread discusses. When do we actually want to
call map on a dictionary? I feel like mapValue should be the default.

I'm wondering if `Dictionary<String,Int>.Element`should be `Value`, instead
of `(Key,Value)`.

With an array you can do this:

for (key, value) in ["a", "b", "c"].enumerate() {

    print(key, value) // 0 a, 1 b, 2 c

}

Shouldn't the equivalent for a dictionary be this?

for (key, value) in ["a": 0, "b": 1, "c": 2].enumerate() {

    print(key, value) // a 0, b 1, c 2

}

Not this:

for (key, value) in ["a": 0, "b": 1, "c": 2] {

    print(key, value) // a 0, b 1, c 2

}

Presumably the old syntax would produce only values:

for value in ["a": 0, "b": 1, "c": 2] {

    print(value) // 0, 1, 2

}

You can still iterate the keys like this:

for key in ["a": 0, "b": 1, "c": 2].keys {

    print(key) // a, b, c

}

I think I understand the reasoning for Dictionary the way it is, but I
think it only provides consistency to the implementation, not the interface.

The result of all this being that Dictionary is more consistent (IMO), and
map would work on the values of all collection types, not a mix of values
and key/value pairs.

What do you think?

···

On Thu, Apr 14, 2016 at 9:07 AM, Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

on Wed Apr 13 2016, Nate Cook <swift-evolution@swift.org> wrote:

> On Apr 13, 2016, at 12:02 PM, Jacob Bandes-Storch via swift-evolution > > <swift-evolution@swift.org> wrote:
>
> To adhere to the API Design Guidelines, I think it should be named
> "mappingValues", right?
>
> On Wed, Apr 13, 2016 at 4:23 AM, Vladimir.S via swift-evolution > > <swift-evolution@swift.org> wrote:
>
> As for mapKeys and many values into a single key. I believe we
should have a
> choice - do we expect multiply values for the same key or not.
Just like
> "+" and integer overflow : by default it raises the error, but
if "&+" -
> we expect the overflow. I can imagine situations when it is ok
for me to
> have different values for the same key(if I don't care which of
values
> should be for that key in result dictionary).
> So my proposal is some additional mapKey(allowMultiplyValues:
true)
> {...} or in any other form/name.
>
> There's a proposal (awaiting merging) to add Dictionary initializers and
methods
> that work with key/value pairs. These provide different ways of dealing
with the
> duplicate key issue after a call to the regular Collection.map method.
>
>
https://github.com/natecook1000/swift-evolution/blob/natecook-dictionary-merge/proposals/0000-add-sequence-based-init-and-merge-to-dictionary.md

Ah, yes, that reminds me of your answer to the question about whether we
need a “uniquingKeys” label on that init(), which IMO is a good one: no,
we don't need it, because the one without the label traps on duplicate
keys.

> I'd be interested in a `mapValues` or `transformValues` method that
would modify
> values in place while leaving keys alone.

Is that enough of an improvement over

   Dictionary(d.lazy.map { (k,v) in (k, transform(v)) })

(once we get that initializer) to make it worth expanding the Dictionary
API?

> Another useful method (that could be used to build mapValues more
efficiently)
> would be `Dictionary.updateValue(value: Value, at index:
DictionaryIndex)`, so
> you could write:
>
> var dict = ["a": 1, "b": 2, "c": 3]
> if let i = dict.index(where: { $0.value == 3 }) {
> dict.updateValue(100, at: i)
> }
> // dict == ["a": 1, "b": 2, "c": 100]

Indeed it would!

> -Nate
>
> On 13.04.2016 13:38, Ross O'Brien via swift-evolution wrote:
>
> +1 on mapValues.
>
> DictionaryLiteral already throws an exception if it includes
> duplicate
> keys, so I'd expect mapKeys to throw an error if multiple
source
> keys
> mapped to the same destination key.
>
> On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via > > swift-evolution > > <swift-evolution@swift.org > > <mailto:swift-evolution@swift.org>> > > wrote:
>
> I'm +1 for adding mapValues. Very useful functionality and
trivial
> to
> implement.
>
> > > I.e. I suggest to implement and mapKeys() also. It could
be also
> useful in some situations.
> > `mapKeys` is much more dangerous, because you could end up
mapping
> many values into a single key. You kind of need to combine
the
> values somehow. Perhaps:
> >
> > extension Dictionary {
> > func mapValues__(_ valueTransform: @noescape Value throws
> ->OutValue) rethrows ->[Key: OutValue] { … }
> >
> > func mapKeys__(_ keyTransform: @noescape Key throws
->OutKey)
> rethrows ->[OutKey: [Value]] { … }
> >
> > // Possibly flatMap variants, too?
> > }
> >
> > extension Dictionary where Value: Sequence {
> > func reduceValues__(_ initial: OutValue, combine: @noescape
> (OutValue, Value.Iterator.Element) throws ->OutValue)
rethrows ->
> [Key:
> OutValue] {
> > return mapValues { $0.reduce(initial, combine: combine) }
> > }
> > }
> >
> > Which you would end up using like this:
> >
> > let wordFrequencies: [String: Int] = …
> > let firstLetterFrequencies: [Character: Int] =
> wordFrequencies.mapKeys { $0.characters.first!
}.reduceValues(0,
> combine: +)
> >
> > --
> > Brent Royal-Gordon
> > Architechies
> >
> >
> >
> >______
>
> --
> Miguel Angel Quinones
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #8

I'm wondering if `Dictionary<String,Int>.Element`should be `Value`,
instead of `(Key,Value)`.

I think(?) it's fairly unprecedented. Python sets the
*opposite* precedent, FWIW (a dict is a sequence of its keys).

One possible upside is that Dictionary becomes a MutableCollection.

With an array you can do this:

for (key, value) in ["a", "b", "c"].enumerate() {

print(key, value) // 0 a, 1 b, 2 c

}

Shouldn't the equivalent for a dictionary be this?

for (key, value) in ["a": 0, "b": 1, "c": 2].enumerate() {

print(key, value) // a 0, b 1, c 2

}

Not unless you can come up with a generic definition of the semantics of
enumerate() that allows the first element of the pair to be the index in
some cases and the key in others. Right now, enumerate() works on
sequences, which you would probably have to drop. Also, don't forget,
Dictionaries still need indices distinct from their keys, because you
shouldn't have to do a hash lookup just to advance to the next element.

Not this:

for (key, value) in ["a": 0, "b": 1, "c": 2] {

print(key, value) // a 0, b 1, c 2

}

Presumably the old syntax would produce only values:

for value in ["a": 0, "b": 1, "c": 2] {

print(value) // 0, 1, 2

}

You can still iterate the keys like this:

for key in ["a": 0, "b": 1, "c": 2].keys {

print(key) // a, b, c

}

To get them both, I suppose you'd write zip(d.keys, d.values).

I think I understand the reasoning for Dictionary the way it is, but I
think it only provides consistency to the implementation, not the
interface.

Dictionary was not defined the way it is for implementation reasons. In
fact, the keys and values are currently stored in separate arrays. You
are suggesting a different conceptual model for dictionaries, and it
might even be an improvement, but that doesn't mean the current design
is implementation-driven.

The result of all this being that Dictionary is more consistent (IMO), and map
would work on the values of all collection types, not a mix of values and
key/value pairs.

What do you think?

It's an interesting idea, worthy of consideration. I suspect there are
probably lots more arguments to be made here, that haven't occurred to
either of us yet, and maybe some serious downsides. I hope to hear more
about it from others.

···

on Wed Apr 13 2016, Andrew Bennett <cacoyi-AT-gmail.com> wrote:

On Thu, Apr 14, 2016 at 9:07 AM, Dave Abrahams via swift-evolution > <swift-evolution@swift.org> wrote:

    on Wed Apr 13 2016, Nate Cook <swift-evolution@swift.org> wrote:

    > On Apr 13, 2016, at 12:02 PM, Jacob Bandes-Storch via swift-evolution > > <swift-evolution@swift.org> wrote:
    >
    > To adhere to the API Design Guidelines, I think it should be named
    > "mappingValues", right?
    >
    > On Wed, Apr 13, 2016 at 4:23 AM, Vladimir.S via swift-evolution > > <swift-evolution@swift.org> wrote:
    >
    > As for mapKeys and many values into a single key. I believe we should have
    a
    > choice - do we expect multiply values for the same key or not. Just like
    > "+" and integer overflow : by default it raises the error, but if "&+" -
    > we expect the overflow. I can imagine situations when it is ok for me to
    > have different values for the same key(if I don't care which of values
    > should be for that key in result dictionary).
    > So my proposal is some additional mapKey(allowMultiplyValues: true)
    > {...} or in any other form/name.
    >
    > There's a proposal (awaiting merging) to add Dictionary initializers and
    methods
    > that work with key/value pairs. These provide different ways of dealing
    with the
    > duplicate key issue after a call to the regular Collection.map method.
    >
    >
    https://github.com/natecook1000/swift-evolution/blob/natecook-dictionary-merge/proposals/0000-add-sequence-based-init-and-merge-to-dictionary.md

    Ah, yes, that reminds me of your answer to the question about whether we
    need a “uniquingKeys” label on that init(), which IMO is a good one: no,
    we don't need it, because the one without the label traps on duplicate
    keys.

    > I'd be interested in a `mapValues` or `transformValues` method that would
    modify
    > values in place while leaving keys alone.

    Is that enough of an improvement over

    Dictionary(d.lazy.map { (k,v) in (k, transform(v)) })

    (once we get that initializer) to make it worth expanding the Dictionary
    API?

    > Another useful method (that could be used to build mapValues more
    efficiently)
    > would be `Dictionary.updateValue(value: Value, at index: DictionaryIndex)
    `, so
    > you could write:
    >
    > var dict = ["a": 1, "b": 2, "c": 3]
    > if let i = dict.index(where: { $0.value == 3 }) {
    > dict.updateValue(100, at: i)
    > }
    > // dict == ["a": 1, "b": 2, "c": 100]

    Indeed it would!

    > -Nate
    >
    > On 13.04.2016 13:38, Ross O'Brien via swift-evolution wrote:
    >
    > +1 on mapValues.
    >
    > DictionaryLiteral already throws an exception if it includes
    > duplicate
    > keys, so I'd expect mapKeys to throw an error if multiple source
    > keys
    > mapped to the same destination key.
    >
    > On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via > > swift-evolution > > <swift-evolution@swift.org > > <mailto:swift-evolution@swift.org>> > > wrote:
    >
    > I'm +1 for adding mapValues. Very useful functionality and trivial
    > to
    > implement.
    >
    > > > I.e. I suggest to implement and mapKeys() also. It could be also
    > useful in some situations.
    > > `mapKeys` is much more dangerous, because you could end up mapping
    > many values into a single key. You kind of need to combine the
    > values somehow. Perhaps:
    > >
    > > extension Dictionary {
    > > func mapValues__(_ valueTransform: @noescape Value throws
    > ->OutValue) rethrows ->[Key: OutValue] { … }
    > >
    > > func mapKeys__(_ keyTransform: @noescape Key throws ->OutKey)
    > rethrows ->[OutKey: [Value]] { … }
    > >
    > > // Possibly flatMap variants, too?
    > > }
    > >
    > > extension Dictionary where Value: Sequence {
    > > func reduceValues__(_ initial: OutValue, combine: @noescape
    > (OutValue, Value.Iterator.Element) throws ->OutValue) rethrows ->
    > [Key:
    > OutValue] {
    > > return mapValues { $0.reduce(initial, combine: combine) }
    > > }
    > > }
    > >
    > > Which you would end up using like this:
    > >
    > > let wordFrequencies: [String: Int] = …
    > > let firstLetterFrequencies: [Character: Int] =
    > wordFrequencies.mapKeys { $0.characters.first! }.reduceValues(0,
    > combine: +)
    > >
    > > --
    > > Brent Royal-Gordon
    > > Architechies
    > >
    > >
    > >
    > >______
    >
    > --
    > Miguel Angel Quinones
    >
    > _______________________________________________
    > swift-evolution mailing list
    > swift-evolution@swift.org
    > <mailto:swift-evolution@swift.org>
    > https://lists.swift.org/mailman/listinfo/swift-evolution
    >
    > _______________________________________________
    > swift-evolution mailing list
    > swift-evolution@swift.org
    > https://lists.swift.org/mailman/listinfo/swift-evolution
    >
    > _______________________________________________
    > swift-evolution mailing list
    > swift-evolution@swift.org
    > https://lists.swift.org/mailman/listinfo/swift-evolution
    >
    > _______________________________________________
    > swift-evolution mailing list
    > swift-evolution@swift.org
    > https://lists.swift.org/mailman/listinfo/swift-evolution
    >
    > _______________________________________________
    > swift-evolution mailing list
    > swift-evolution@swift.org
    > https://lists.swift.org/mailman/listinfo/swift-evolution

    --
    Dave

    _______________________________________________
    swift-evolution mailing list
    swift-evolution@swift.org
    https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave


(Andrew Bennett) #9

This is a clarification on what I meant (I haven't had much time to test
it, but I think it's representative):

https://gist.github.com/therealbnut/c223d90a34bb14448b65fc6cc0ec70ac

Sorry if I've derailed this conversation, I'm in support of mapValues, but
I feel like map itself should be mapping the values. Let me know if I
should discuss this in another thread.

···

On Fri, Apr 15, 2016 at 2:23 AM, Dave Abrahams <dabrahams@apple.com> wrote:

on Wed Apr 13 2016, Andrew Bennett <cacoyi-AT-gmail.com> wrote:

> I'm wondering if `Dictionary<String,Int>.Element`should be `Value`,
> instead of `(Key,Value)`.

I think(?) it's fairly unprecedented. Python sets the
*opposite* precedent, FWIW (a dict is a sequence of its keys).

One possible upside is that Dictionary becomes a MutableCollection.

> With an array you can do this:
>
> for (key, value) in ["a", "b", "c"].enumerate() {
>
> print(key, value) // 0 a, 1 b, 2 c
>
> }
>
> Shouldn't the equivalent for a dictionary be this?
>
> for (key, value) in ["a": 0, "b": 1, "c": 2].enumerate() {
>
> print(key, value) // a 0, b 1, c 2
>
> }

Not unless you can come up with a generic definition of the semantics of
enumerate() that allows the first element of the pair to be the index in
some cases and the key in others. Right now, enumerate() works on
sequences, which you would probably have to drop. Also, don't forget,
Dictionaries still need indices distinct from their keys, because you
shouldn't have to do a hash lookup just to advance to the next element.

> Not this:
>
> for (key, value) in ["a": 0, "b": 1, "c": 2] {
>
> print(key, value) // a 0, b 1, c 2
>
> }
>
> Presumably the old syntax would produce only values:
>
> for value in ["a": 0, "b": 1, "c": 2] {
>
> print(value) // 0, 1, 2
>
> }
>
> You can still iterate the keys like this:
>
> for key in ["a": 0, "b": 1, "c": 2].keys {
>
> print(key) // a, b, c
>
> }

To get them both, I suppose you'd write zip(d.keys, d.values).

> I think I understand the reasoning for Dictionary the way it is, but I
> think it only provides consistency to the implementation, not the
> interface.

Dictionary was not defined the way it is for implementation reasons. In
fact, the keys and values are currently stored in separate arrays. You
are suggesting a different conceptual model for dictionaries, and it
might even be an improvement, but that doesn't mean the current design
is implementation-driven.

> The result of all this being that Dictionary is more consistent (IMO),
and map
> would work on the values of all collection types, not a mix of values and
> key/value pairs.
>
> What do you think?

It's an interesting idea, worthy of consideration. I suspect there are
probably lots more arguments to be made here, that haven't occurred to
either of us yet, and maybe some serious downsides. I hope to hear more
about it from others.

> On Thu, Apr 14, 2016 at 9:07 AM, Dave Abrahams via swift-evolution > > <swift-evolution@swift.org> wrote:
>
> on Wed Apr 13 2016, Nate Cook <swift-evolution@swift.org> wrote:
>
> > On Apr 13, 2016, at 12:02 PM, Jacob Bandes-Storch via > swift-evolution > > > <swift-evolution@swift.org> wrote:
> >
> > To adhere to the API Design Guidelines, I think it should be named
> > "mappingValues", right?
> >
> > On Wed, Apr 13, 2016 at 4:23 AM, Vladimir.S via swift-evolution > > > <swift-evolution@swift.org> wrote:
> >
> > As for mapKeys and many values into a single key. I believe we
should have
> a
> > choice - do we expect multiply values for the same key or not.
Just like
> > "+" and integer overflow : by default it raises the error, but if
"&+" -
> > we expect the overflow. I can imagine situations when it is ok for
me to
> > have different values for the same key(if I don't care which of
values
> > should be for that key in result dictionary).
> > So my proposal is some additional mapKey(allowMultiplyValues: true)
> > {...} or in any other form/name.
> >
> > There's a proposal (awaiting merging) to add Dictionary
initializers and
> methods
> > that work with key/value pairs. These provide different ways of
dealing
> with the
> > duplicate key issue after a call to the regular Collection.map
method.
> >
> >
>
https://github.com/natecook1000/swift-evolution/blob/natecook-dictionary-merge/proposals/0000-add-sequence-based-init-and-merge-to-dictionary.md
>
> Ah, yes, that reminds me of your answer to the question about
whether we
> need a “uniquingKeys” label on that init(), which IMO is a good one:
no,
> we don't need it, because the one without the label traps on
duplicate
> keys.
>
> > I'd be interested in a `mapValues` or `transformValues` method
that would
> modify
> > values in place while leaving keys alone.
>
> Is that enough of an improvement over
>
> Dictionary(d.lazy.map { (k,v) in (k, transform(v)) })
>
> (once we get that initializer) to make it worth expanding the
Dictionary
> API?
>
> > Another useful method (that could be used to build mapValues more
> efficiently)
> > would be `Dictionary.updateValue(value: Value, at index:
DictionaryIndex)
> `, so
> > you could write:
> >
> > var dict = ["a": 1, "b": 2, "c": 3]
> > if let i = dict.index(where: { $0.value == 3 }) {
> > dict.updateValue(100, at: i)
> > }
> > // dict == ["a": 1, "b": 2, "c": 100]
>
> Indeed it would!
>
> > -Nate
> >
> > On 13.04.2016 13:38, Ross O'Brien via swift-evolution wrote:
> >
> > +1 on mapValues.
> >
> > DictionaryLiteral already throws an exception if it includes
> > duplicate
> > keys, so I'd expect mapKeys to throw an error if multiple source
> > keys
> > mapped to the same destination key.
> >
> > On Wed, Apr 13, 2016 at 11:28 AM, Miguel Angel Quinones via > > > swift-evolution > > > <swift-evolution@swift.org > > > <mailto:swift-evolution@swift.org>> > > > wrote:
> >
> > I'm +1 for adding mapValues. Very useful functionality and trivial
> > to
> > implement.
> >
> > > > I.e. I suggest to implement and mapKeys() also. It could be
also
> > useful in some situations.
> > > `mapKeys` is much more dangerous, because you could end up
mapping
> > many values into a single key. You kind of need to combine the
> > values somehow. Perhaps:
> > >
> > > extension Dictionary {
> > > func mapValues__(_ valueTransform: @noescape Value throws
> > ->OutValue) rethrows ->[Key: OutValue] { … }
> > >
> > > func mapKeys__(_ keyTransform: @noescape Key throws ->OutKey)
> > rethrows ->[OutKey: [Value]] { … }
> > >
> > > // Possibly flatMap variants, too?
> > > }
> > >
> > > extension Dictionary where Value: Sequence {
> > > func reduceValues__(_ initial: OutValue, combine: @noescape
> > (OutValue, Value.Iterator.Element) throws ->OutValue) rethrows ->
> > [Key:
> > OutValue] {
> > > return mapValues { $0.reduce(initial, combine: combine) }
> > > }
> > > }
> > >
> > > Which you would end up using like this:
> > >
> > > let wordFrequencies: [String: Int] = …
> > > let firstLetterFrequencies: [Character: Int] =
> > wordFrequencies.mapKeys { $0.characters.first! }.reduceValues(0,
> > combine: +)
> > >
> > > --
> > > Brent Royal-Gordon
> > > Architechies
> > >
> > >
> > >
> > >______
> >
> > --
> > Miguel Angel Quinones
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > <mailto:swift-evolution@swift.org>
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
>
> --
> Dave
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>

--
Dave


(Brent Royal-Gordon) #10

This is a clarification on what I meant (I haven't had much time to test it, but I think it's representative):

https://gist.github.com/therealbnut/c223d90a34bb14448b65fc6cc0ec70ac

There are a number of problems with this:

* Given just an `Index`, you need to be able to calculate the next `Index`, and check if it lies before the `endIndex`. The complexity of this is hidden in your example because you're relying on a traditional `DictionaryIndex` to perform these tasks. Thus, the `Index` type in your dictionary design would be some sort of wrapper-around-the-key, not the key itself. That's the source of the `subscript(_: Index) -> Value` which apparently confused you.

* Generic `SequenceType` and `CollectionType` operations now use only the values, not the keys and values. That means that `Array(newDictionary)` gets you an array of values, `filter` returns an array of values, `reduce` operates only on the values, etc; all of these operations throw away the keys. That's not necessarily wrong, but I'm not sure that you're aware of it.

* Your `map` is overloading `SequenceType.map` merely by return type. That means you can no longer assign it directly to a newly-declared variable, or use it in many other contexts where the type has to be inferred.

* Your `enumerate` is similarly overloading by return type. It's also further confusing an already confused semantic. `enumerate` does not pair elements with their indices; it pairs them with integers starting at zero. This *happens* to correspond to their array indices, but that's not necessarily true of any other Collection. (I'm going to start another thread about this.)

Basically, in this conception of Dictionary:

* The keys are disposable and the values are the important part—most operations on a Dictionary would throw away the keys and use only the values.
* Index still would not be a Key—it would be some kind of instance which wrapped or converted into a Key.
* You still would need to name Dictionary-creating `map` and similar functions differently from their Array-generating cousins.
* Iterating over keys and values together would require some kind of Dictionary-specific API, not something that was available on other collections or sequences.

Maybe that would be an improvement, but I'm skeptical.

···

--
Brent Royal-Gordon
Architechies


(Andrew Bennett) #11

Thanks Brent for your in-depth response - I've responded in another thread
so we don't pollute this one (sorry Jonathan):
http://thread.gmane.org/gmane.comp.lang.swift.evolution/14665

I think mapValues is great, and really needed in Swift.

I'm a definite +1 on a version of map that only maps the values.

···

On Sat, Apr 16, 2016 at 7:56 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

> This is a clarification on what I meant (I haven't had much time to test
it, but I think it's representative):
>
> https://gist.github.com/therealbnut/c223d90a34bb14448b65fc6cc0ec70ac

There are a number of problems with this:

* Given just an `Index`, you need to be able to calculate the next
`Index`, and check if it lies before the `endIndex`. The complexity of this
is hidden in your example because you're relying on a traditional
`DictionaryIndex` to perform these tasks. Thus, the `Index` type in your
dictionary design would be some sort of wrapper-around-the-key, not the key
itself. That's the source of the `subscript(_: Index) -> Value` which
apparently confused you.

* Generic `SequenceType` and `CollectionType` operations now use only the
values, not the keys and values. That means that `Array(newDictionary)`
gets you an array of values, `filter` returns an array of values, `reduce`
operates only on the values, etc; all of these operations throw away the
keys. That's not necessarily wrong, but I'm not sure that you're aware of
it.

* Your `map` is overloading `SequenceType.map` merely by return type. That
means you can no longer assign it directly to a newly-declared variable, or
use it in many other contexts where the type has to be inferred.

* Your `enumerate` is similarly overloading by return type. It's also
further confusing an already confused semantic. `enumerate` does not pair
elements with their indices; it pairs them with integers starting at zero.
This *happens* to correspond to their array indices, but that's not
necessarily true of any other Collection. (I'm going to start another
thread about this.)

Basically, in this conception of Dictionary:

* The keys are disposable and the values are the important part—most
operations on a Dictionary would throw away the keys and use only the
values.
* Index still would not be a Key—it would be some kind of instance which
wrapped or converted into a Key.
* You still would need to name Dictionary-creating `map` and similar
functions differently from their Array-generating cousins.
* Iterating over keys and values together would require some kind of
Dictionary-specific API, not something that was available on other
collections or sequences.

Maybe that would be an improvement, but I'm skeptical.

--
Brent Royal-Gordon
Architechies