[draft] Introduce Sequence.filteredMap(_:)

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
Authors: Max Moiseev <https://github.com/moiseev&gt;
Review Manager: TBD
Status: Awaiting implementation
<0000-introduce-filteredmap.md · GitHub

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

<0000-introduce-filteredmap.md · GitHub

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name } statement, and the names variable were used elsewhere. The compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> where F: FnMut(Self::Item) -> Option<B>
Scala def collect[B](pf: PartialFunction[A, B]): List[B]
<0000-introduce-filteredmap.md · GitHub compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

<0000-introduce-filteredmap.md · GitHub on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) Does not communicate what happens to nil’s
mapSome(_:) Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

I strongly agree! In fact, I just started writing up a similar proposal the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will filter out results. And it’s not true to the existing usage of ‘flatMap' from other languages; you have to really squint at it to see how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

···

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
Authors: Max Moiseev <https://github.com/moiseev&gt;
Review Manager: TBD
Status: Awaiting implementation
<0000-introduce-filteredmap.md · GitHub

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

<0000-introduce-filteredmap.md · GitHub

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name } statement, and the names variable were used elsewhere. The compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris > mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) > filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# > List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust > fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> > where F: FnMut(Self::Item) -> Option<B>
Scala > def collect[B](pf: PartialFunction[A, B]): List[B]
<0000-introduce-filteredmap.md · GitHub compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

<0000-introduce-filteredmap.md · GitHub on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) > Does not communicate what happens to nil’s
mapSome(_:) > Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) > Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

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

Huge +1

Calling "flatMap" a map + filtering out the nil values was a TERRIBLE idea.

Elviro

···

Il giorno 24 ott 2017, alle ore 00:15, Max Moiseev via swift-evolution <swift-evolution@swift.org> ha scritto:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
Authors: Max Moiseev <https://github.com/moiseev&gt;
Review Manager: TBD
Status: Awaiting implementation
<0000-introduce-filteredmap.md · GitHub

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

<0000-introduce-filteredmap.md · GitHub

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name } statement, and the names variable were used elsewhere. The compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris > mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) > filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# > List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust > fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> > where F: FnMut(Self::Item) -> Option<B>
Scala > def collect[B](pf: PartialFunction[A, B]): List[B]
<0000-introduce-filteredmap.md · GitHub compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

<0000-introduce-filteredmap.md · GitHub on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) > Does not communicate what happens to nil’s
mapSome(_:) > Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) > Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

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

Hi there,

In my opinion, `map` vs `flatMap` confusing comes from implicitly casting
`T` to `T?`.
I believe it make many confusing and problems that compiler allow this
implicitly casting.
1 way of solve this topic that show warning if use implicitly casting `T`
to `T?`. Of course it should be explict casting can avoid this warning.
Such as `x as Int?`, `Int?.some(x)`, `intClosure as (() -> Int?)`.

···

2017-10-24 7:15 GMT+09:00 Max Moiseev via swift-evolution < swift-evolution@swift.org>:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this
functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at moi’s gists · GitHub
seev/2f36376c8ef4c2b1273cff0bfd9c3b95 and is included below for your
convenience.

Max

Introduce Sequence.filteredMap(_:)

   - Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
   - Authors: Max Moiseev <https://github.com/moiseev&gt;
   - Review Manager: TBD
   - Status: Awaiting implementation

<0000-introduce-filteredmap.md · GitHub;
Introduction

We propose to deprecate the controversial version of a Sequence.flatMap method
and provide the same functionality under a different, and potentially more
descriptive, name.

<0000-introduce-filteredmap.md · GitHub;
Motivation

The Swift standard library currently defines 3 distinct overloads for
flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : SequenceOptional.flatMap<U>(_: (Wrapped) -> U?) -> U?Sequence.flatMap<U>(_: (Element) -> U?) -> [U]

The last one, despite being useful in certain situations, can be (and
often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}
func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}

What happens inside getNames is: thanks to the implicit promotion to
Optional, the result of the closure gets wrapped into a .some, then
immediately unwrapped by the implementation of flatMap, and appended to
the result array. All this unnecessary wrapping and unwrapping can be
easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}

It gets even worse when we consider future code modifications, like the
one where Swift 4 introduced a Stringconformance to the Collection protocol.
The following code used to compile (due to the flatMap overload in
question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}

But it no longer does, because now there is a better overload that does
not involve implicit promotion. In this particular case, the compiler error
would be obvious, as it would point at the same line where flatMap is
used. Imagine however if it was just a let names = people.flatMap { $
0.name } statement, and the names variable were used elsewhere. The
compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub
solution

We propose to deprecate the controversial overload of flatMap and
re-introduce the same functionality under a new name. The name being
filteredMap(_:) as we believe it best describes the intent of this
function.

For reference, here are the alternative names from other languages:

   - Haskell, Idris mapMaybe :: (a -> Maybe b) -> [a] -> [b]
   - Ocaml (Core and Batteries) filter_map : 'a t -> f:('a -> 'b option)
   -> 'b t
   - F# List.choose : ('T -> 'U option) -> 'T list -> 'U list
   - Rust fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> where F:
   FnMut(Self::Item) -> Option<B>
   - Scala def collect[B](pf: PartialFunction[A, B]): List[B]

<0000-introduce-filteredmap.md · GitHub
compatibility

Since the old function will still be available (although deprecated) all
the existing code will compile, producing a deprecation warning and a
fix-it.

<0000-introduce-filteredmap.md · GitHub
on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub
on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when
ABI stability is declared, but in the worst case, it will be available in a
deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub
considered

It was attempted in the past to warn about this kind of misuse and do the
right thing instead by means of a deprecated overload with a
non-optional-returning closure. The attempt failed due to another implicit
promotion (this time to Any).

The following alternative names for this function were considered:

   - mapNonNil(_:) Does not communicate what happens to nil’s
   - mapSome(_:) Reads more like «map some elements of the sequence, but
   not the others» rather than «process only the ones that produce an
   Optional.some»
   - filterMap(_:) Does not really follow the naming guidelines and
   doesn’t seem to be common enough to be considered a term of art.

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

I'm not attached to the name filteredMap, but having a specific signature for the optional version makes sense.

I think the problem is that while Optional can be contemplated as a sequence of zero or one items, implicit conversion to Optional means anything can then be interpreted as a collection of one item.

Likewise, if the syntax for passing in closures required any argument label to be specified, it might be clearer for optional vs sequence, but they are not required.

-DW

···

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
Authors: Max Moiseev <https://github.com/moiseev&gt;
Review Manager: TBD
Status: Awaiting implementation
<0000-introduce-filteredmap.md · GitHub

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

<0000-introduce-filteredmap.md · GitHub

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name } statement, and the names variable were used elsewhere. The compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris > mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) > filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# > List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust > fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> > where F: FnMut(Self::Item) -> Option<B>
Scala > def collect[B](pf: PartialFunction[A, B]): List[B]
<0000-introduce-filteredmap.md · GitHub compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

<0000-introduce-filteredmap.md · GitHub on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) > Does not communicate what happens to nil’s
mapSome(_:) > Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) > Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

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

+1 in general. As to the name: since 'map' is used as a term of art,
'filterMap' seems superior to 'filteredMap', which half follows naming
guidelines and half is a term of art; neither is immediately comprehensible
but 'filterMap' can be googled and has precedents in other languages.

···

On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution < swift-evolution@swift.org> wrote:

I strongly agree! In fact, I just started writing up a similar proposal
the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly
descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will
filter out results. And it’s not true to the existing usage of ‘flatMap'
from other languages; you have to really squint at it to see how any
“flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution < > swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this
functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at
0000-introduce-filteredmap.md · GitHub and is
included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

   - Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
   - Authors: Max Moiseev <https://github.com/moiseev&gt;
   - Review Manager: TBD
   - Status: Awaiting implementation

<0000-introduce-filteredmap.md · GitHub;
Introduction

We propose to deprecate the controversial version of a Sequence.flatMap method
and provide the same functionality under a different, and potentially more
descriptive, name.

<0000-introduce-filteredmap.md · GitHub;
Motivation

The Swift standard library currently defines 3 distinct overloads for
flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : SequenceOptional.flatMap<U>(_: (Wrapped) -> U?) -> U?Sequence.flatMap<U>(_: (Element) -> U?) -> [U]

The last one, despite being useful in certain situations, can be (and
often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}
func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}

What happens inside getNames is: thanks to the implicit promotion to
Optional, the result of the closure gets wrapped into a .some, then
immediately unwrapped by the implementation of flatMap, and appended to
the result array. All this unnecessary wrapping and unwrapping can be
easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}

It gets even worse when we consider future code modifications, like the
one where Swift 4 introduced a Stringconformance to the Collection protocol.
The following code used to compile (due to the flatMap overload in
question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}

But it no longer does, because now there is a better overload that does
not involve implicit promotion. In this particular case, the compiler error
would be obvious, as it would point at the same line where flatMap is
used. Imagine however if it was just a let names = people.flatMap { $
0.name } statement, and the names variable were used elsewhere. The
compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub
solution

We propose to deprecate the controversial overload of flatMap and
re-introduce the same functionality under a new name. The name being
filteredMap(_:) as we believe it best describes the intent of this
function.

For reference, here are the alternative names from other languages:

   - Haskell, Idris mapMaybe :: (a -> Maybe b) -> [a] -> [b]
   - Ocaml (Core and Batteries) filter_map : 'a t -> f:('a -> 'b option)
   -> 'b t
   - F# List.choose : ('T -> 'U option) -> 'T list -> 'U list
   - Rust fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> where F:
   FnMut(Self::Item) -> Option<B>
   - Scala def collect[B](pf: PartialFunction[A, B]): List[B]

<0000-introduce-filteredmap.md · GitHub
compatibility

Since the old function will still be available (although deprecated) all
the existing code will compile, producing a deprecation warning and a
fix-it.

<0000-introduce-filteredmap.md · GitHub
on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub
on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when
ABI stability is declared, but in the worst case, it will be available in a
deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub
considered

It was attempted in the past to warn about this kind of misuse and do the
right thing instead by means of a deprecated overload with a
non-optional-returning closure. The attempt failed due to another implicit
promotion (this time to Any).

The following alternative names for this function were considered:

   - mapNonNil(_:) Does not communicate what happens to nil’s
   - mapSome(_:) Reads more like «map some elements of the sequence, but
   not the others» rather than «process only the ones that produce an
   Optional.some»
   - filterMap(_:) Does not really follow the naming guidelines and
   doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

+1 from me as well.

···

——
Adrian Kashivskyy

On 24 Oct 2017, 00:24 +0200, BJ Homer via swift-evolution <swift-evolution@swift.org>, wrote:

I strongly agree! In fact, I just started writing up a similar proposal the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will filter out results. And it’s not true to the existing usage of ‘flatMap' from other languages; you have to really squint at it to see how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

> On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:
>
> Hi swift-evolution!
>
> I would like to propose the following change to the standard library:
>
> deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.
>
> The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.
>
> Max
>
> Introduce Sequence.filteredMap(_:)
>
> • Proposal: SE-NNNN
> • Authors: Max Moiseev
> • Review Manager: TBD
> • Status: Awaiting implementation
>
> Introduction
> We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.
> Motivation
> The Swift standard library currently defines 3 distinct overloads for flatMap:
> Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
> where S : Sequence
> Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
> Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
> The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:
> struct Person {
> var age: Int
> var name: String
> }
>
> func getAges(people: [Person]) -> [Int] {
> return people.flatMap { $0.age }
> }
> What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.
> func getAges(people: [Person]) -> [Int] {
> return people.map { $0.age }
> }
> It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).
> func getNames(people: [Person]) -> [String] {
> return people.flatMap { $0.name }
> }
> But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name } statement, and the names variable were used elsewhere. The compiler error would be misleading.
> Proposed solution
> We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.
> For reference, here are the alternative names from other languages:
>
> • Haskell, Idris mapMaybe :: (a -> Maybe b) -> [a] -> [b]
> • Ocaml (Core and Batteries) filter_map : 'a t -> f:('a -> 'b option) -> 'b t
> • F# List.choose : ('T -> 'U option) -> 'T list -> 'U list
> • Rust fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> where F: FnMut(Self::Item) -> Option<B>
> • Scala def collect[B](pf: PartialFunction[A, B]): List[B]
>
> Source compatibility
> Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.
> Effect on ABI stability
> This is an additive API change, and does not affect ABI stability.
> Effect on API resilience
> Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.
> Alternatives considered
> It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).
> The following alternative names for this function were considered:
>
> • mapNonNil(_:) Does not communicate what happens to nil’s
> • mapSome(_:) Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
> • filterMap(_:) Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

Calling "flatMap" a map + filtering out the nil values was a TERRIBLE idea.

maybe I’m the one who’s getting this all wrong, but afaik the purpose of flatMap isn’t filtering out nil at all, but just a combination of map & flatten…
Optional<T> is like Array<T> with a maximal capacity of one (so it can either contain a single object, or it’s empty), and in this interpretation, there’s nothing wrong with the behavior.

Instead, I think Nobuo Saito is right, and that a renaming to filteredMap is only fighting symptoms, and not the cause (but I’m not that sure if there’s a good way to tackle the cause).
Besides that, all those alternative names also have potential for confusion:
Imho it isn’t intuitive what is filtered out — and don’t forget that the result of flatMap can contain nil elements…

If the biggest problem of flatMap is that people who don’t understand it write code that isn’t optimal (but still does the right thing!), I don’t think there is any change needed. I’m not even sure that that wrapping/unwrapping is actually done, because it should be discoverable by the compiler.

It would be nice if there was an way to warn in places where flatMap could be replaced with map, though (but imho this special warning shouldn’t be checked by the compiler).

- Tino

It occurred to me that filteringMap(_:) should be even more descriptive, still conform to the guidelines, although similarly unprecedented and un-googlable.

Max

···

On Oct 23, 2017, at 3:52 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

+1 in general. As to the name: since 'map' is used as a term of art, 'filterMap' seems superior to 'filteredMap', which half follows naming guidelines and half is a term of art; neither is immediately comprehensible but 'filterMap' can be googled and has precedents in other languages.
On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I strongly agree! In fact, I just started writing up a similar proposal the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will filter out results. And it’s not true to the existing usage of ‘flatMap' from other languages; you have to really squint at it to see how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
Authors: Max Moiseev <https://github.com/moiseev&gt;
Review Manager: TBD
Status: Awaiting implementation
<0000-introduce-filteredmap.md · GitHub

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

<0000-introduce-filteredmap.md · GitHub

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name <http://0.name/&gt; } statement, and the names variable were used elsewhere. The compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris >> mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) >> filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# >> List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust >> fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> >> where F: FnMut(Self::Item) -> Option<B>
Scala >> def collect[B](pf: PartialFunction[A, B]): List[B]
<0000-introduce-filteredmap.md · GitHub compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

<0000-introduce-filteredmap.md · GitHub on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) >> Does not communicate what happens to nil’s
mapSome(_:) >> Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) >> Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

The reason `flatMap(_:)` has its name is for code like this:

    let nestedArray = [[1, 2, 3], [4, 5, 6]]
    let flatArray = nestedArray.flatMap {
        $0
    }

    print(flatArray) // prints [1, 2, 3, 4, 5, 6]

    let anotherNestedArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
    let anotherFlatArray = anotherNestedArray.flatMap {
        $0
    }

    print(anotherFlatArray) // prints [[1, 2], [3, 4], [5, 6], [7, 8]]

Do your examples from other languages have this behavior of flattening
nested sequences by 1 layer? Do you think it makes sense to have this
effect as part of your proposed rename?

I feel like the name `filteredMap(_:)` implies that the result should keep
the same structure as the original Sequence type which doesn't allow for
the flattening.

If these 2 pieces of functionality weren't already a part of one function
or if you proposal had a good solution to address this other part
functionality, I would be accepting of a name change. I haven't ever been a
fan of flatMap(_:) having 2 jobs, but it has been this way since Swift 1 or
2.

Also, I don't think the name `filterMap` makes sense with Swift naming
conventions. To me it implies that you are going to map in place vs the
filtered makes it clear you are creating a new Array to return. I really
like the sort() vs sorted() naming convention that is already the standard
for Swift code and would rather any name changes stick to that.

···

On Mon, Oct 23, 2017 at 4:52 PM, Xiaodi Wu via swift-evolution < swift-evolution@swift.org> wrote:

+1 in general. As to the name: since 'map' is used as a term of art,
'filterMap' seems superior to 'filteredMap', which half follows naming
guidelines and half is a term of art; neither is immediately comprehensible
but 'filterMap' can be googled and has precedents in other languages.

On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution < > swift-evolution@swift.org> wrote:

I strongly agree! In fact, I just started writing up a similar proposal
the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not
particularly descriptive. It’s certainly not obvious to newcomers that
‘flatMap’ will filter out results. And it’s not true to the existing usage
of ‘flatMap' from other languages; you have to really squint at it to see
how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution < >> swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this
functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at moiseev’s gists · GitHub
2f36376c8ef4c2b1273cff0bfd9c3b95 and is included below for your
convenience.

Max

Introduce Sequence.filteredMap(_:)

   - Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
   - Authors: Max Moiseev <https://github.com/moiseev&gt;
   - Review Manager: TBD
   - Status: Awaiting implementation

<0000-introduce-filteredmap.md · GitHub;
Introduction

We propose to deprecate the controversial version of a Sequence.flatMap method
and provide the same functionality under a different, and potentially more
descriptive, name.

<0000-introduce-filteredmap.md · GitHub;
Motivation

The Swift standard library currently defines 3 distinct overloads for
flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : SequenceOptional.flatMap<U>(_: (Wrapped) -> U?) -> U?Sequence.flatMap<U>(_: (Element) -> U?) -> [U]

The last one, despite being useful in certain situations, can be (and
often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}
func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}

What happens inside getNames is: thanks to the implicit promotion to
Optional, the result of the closure gets wrapped into a .some, then
immediately unwrapped by the implementation of flatMap, and appended to
the result array. All this unnecessary wrapping and unwrapping can be
easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}

It gets even worse when we consider future code modifications, like the
one where Swift 4 introduced a Stringconformance to the Collection protocol.
The following code used to compile (due to the flatMap overload in
question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}

But it no longer does, because now there is a better overload that does
not involve implicit promotion. In this particular case, the compiler error
would be obvious, as it would point at the same line where flatMap is
used. Imagine however if it was just a let names = people.flatMap { $
0.name } statement, and the names variable were used elsewhere. The
compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub
solution

We propose to deprecate the controversial overload of flatMap and
re-introduce the same functionality under a new name. The name being
filteredMap(_:) as we believe it best describes the intent of this
function.

For reference, here are the alternative names from other languages:

   - Haskell, Idris mapMaybe :: (a -> Maybe b) -> [a] -> [b]
   - Ocaml (Core and Batteries) filter_map : 'a t -> f:('a -> 'b
   option) -> 'b t
   - F# List.choose : ('T -> 'U option) -> 'T list -> 'U list
   - Rust fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> where
   F: FnMut(Self::Item) -> Option<B>
   - Scala def collect[B](pf: PartialFunction[A, B]): List[B]

<0000-introduce-filteredmap.md · GitHub
compatibility

Since the old function will still be available (although deprecated) all
the existing code will compile, producing a deprecation warning and a
fix-it.

<0000-introduce-filteredmap.md · GitHub
on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub
on API resilience

Ideally, the deprecated flatMap overload would not exist at the time
when ABI stability is declared, but in the worst case, it will be available
in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub
considered

It was attempted in the past to warn about this kind of misuse and do the
right thing instead by means of a deprecated overload with a
non-optional-returning closure. The attempt failed due to another implicit
promotion (this time to Any).

The following alternative names for this function were considered:

   - mapNonNil(_:) Does not communicate what happens to nil’s
   - mapSome(_:) Reads more like «map some elements of the sequence,
   but not the others» rather than «process only the ones that produce an
   Optional.some»
   - filterMap(_:) Does not really follow the naming guidelines and
   doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

The reason `flatMap(_:)` has its name is for code like this:

    let nestedArray = [[1, 2, 3], [4, 5, 6]]
    let flatArray = nestedArray.flatMap {
        $0
    }

    print(flatArray) // prints [1, 2, 3, 4, 5, 6]

    let anotherNestedArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
    let anotherFlatArray = anotherNestedArray.flatMap {
        $0
    }

    print(anotherFlatArray) // prints [[1, 2], [3, 4], [5, 6], [7, 8]]

Do your examples from other languages have this behavior of flattening
nested sequences by 1 layer? Do you think it makes sense to have this
effect as part of your proposed rename?

I feel like the name `filteredMap(_:)` implies that the result should keep
the same structure as the original Sequence type which doesn't allow for
the flattening.

If these 2 pieces of functionality weren't already a part of one function
or if you proposal had a good solution to address this other part
functionality, I would be accepting of a name change. I haven't ever been a
fan of flatMap(_:) having 2 jobs, but it has been this way since Swift 1 or
2.

`flatMap` is not going away or being renamed. One particular overload is
being renamed so that the other overloads work as expected.

Also, I don't think the name `filterMap` makes sense with Swift naming
conventions. To me it implies that you are going to map in place vs the
filtered makes it clear you are creating a new Array to return. I really
like the sort() vs sorted() naming convention that is already the standard
for Swift code and would rather any name changes stick to that.

`map`, `filter` and `flatMap` are settled exceptions to the Swift naming
conventions because they are terms of art, and that's not changing going
forward.

···

On Mon, Oct 23, 2017 at 6:11 PM, Braden Scothern <bradenscothern@gmail.com> wrote:

I agree with Xiaodi; I like ‘filterMap’ more than ‘filteredMap’. But both are superior to ‘flatMap’ in this context.

-BJ

···

On Oct 23, 2017, at 5:22 PM, Max Moiseev <moiseev@apple.com> wrote:

It occurred to me that filteringMap(_:) should be even more descriptive, still conform to the guidelines, although similarly unprecedented and un-googlable.

Max

On Oct 23, 2017, at 3:52 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

+1 in general. As to the name: since 'map' is used as a term of art, 'filterMap' seems superior to 'filteredMap', which half follows naming guidelines and half is a term of art; neither is immediately comprehensible but 'filterMap' can be googled and has precedents in other languages.
On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution <swift-evolution@swift.org> wrote:

I strongly agree! In fact, I just started writing up a similar proposal the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will filter out results. And it’s not true to the existing usage of ‘flatMap' from other languages; you have to really squint at it to see how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN
Authors: Max Moiseev
Review Manager: TBD
Status: Awaiting implementation
Introduction

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

Motivation

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name } statement, and the names variable were used elsewhere. The compiler error would be misleading.

Proposed solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris >>>> mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) >>>> filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# >>>> List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust >>>> fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> >>>> where F: FnMut(Self::Item) -> Option<B>
Scala >>>> def collect[B](pf: PartialFunction[A, B]): List[B]
Source compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

Effect on ABI stability

This is an additive API change, and does not affect ABI stability.

Effect on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

Alternatives considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) >>>> Does not communicate what happens to nil’s
mapSome(_:) >>>> Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) >>>> Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

I also cast my vote for filterMap. It’s concise, understandable and is kind of a term of art also.

···

On 24 Oct 2017, at 02:56, BJ Homer via swift-evolution <swift-evolution@swift.org> wrote:

I agree with Xiaodi; I like ‘filterMap’ more than ‘filteredMap’. But both are superior to ‘flatMap’ in this context.

-BJ

On Oct 23, 2017, at 5:22 PM, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

It occurred to me that filteringMap(_:) should be even more descriptive, still conform to the guidelines, although similarly unprecedented and un-googlable.

Max

On Oct 23, 2017, at 3:52 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

+1 in general. As to the name: since 'map' is used as a term of art, 'filterMap' seems superior to 'filteredMap', which half follows naming guidelines and half is a term of art; neither is immediately comprehensible but 'filterMap' can be googled and has precedents in other languages.
On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I strongly agree! In fact, I just started writing up a similar proposal the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will filter out results. And it’s not true to the existing usage of ‘flatMap' from other languages; you have to really squint at it to see how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
Authors: Max Moiseev <https://github.com/moiseev&gt;
Review Manager: TBD
Status: Awaiting implementation
<0000-introduce-filteredmap.md · GitHub

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

<0000-introduce-filteredmap.md · GitHub

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name <http://0.name/&gt; } statement, and the names variable were used elsewhere. The compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris >>>> mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) >>>> filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# >>>> List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust >>>> fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> >>>> where F: FnMut(Self::Item) -> Option<B>
Scala >>>> def collect[B](pf: PartialFunction[A, B]): List[B]
<0000-introduce-filteredmap.md · GitHub compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

<0000-introduce-filteredmap.md · GitHub on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) >>>> Does not communicate what happens to nil’s
mapSome(_:) >>>> Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) >>>> Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

Calling "flatMap" a map + filtering out the nil values was a TERRIBLE idea.

maybe I’m the one who’s getting this all wrong, but afaik the purpose of flatMap isn’t filtering out nil at all, but just a combination of map & flatten…
Optional<T> is like Array<T> with a maximal capacity of one (so it can either contain a single object, or it’s empty), and in this interpretation, there’s nothing wrong with the behavior.

Instead, I think Nobuo Saito is right, and that a renaming to filteredMap is only fighting symptoms, and not the cause (but I’m not that sure if there’s a good way to tackle the cause).
Besides that, all those alternative names also have potential for confusion:
Imho it isn’t intuitive what is filtered out — and don’t forget that the result of flatMap can contain nil elements…

If the biggest problem of flatMap is that people who don’t understand it write code that isn’t optimal (but still does the right thing!), I don’t think there is any change needed. I’m not even sure that that wrapping/unwrapping is actually done, because it should be discoverable by the compiler.

It can be seen that the generated SIL is more complicated for a flatMap case, the microbenchmark also shows that it’s about 3x slower than the map. But even if that can be optimized away, don’t you find a String example from the proposal even a little convincing?

It would be nice if there was an way to warn in places where flatMap could be replaced with map, though (but imho this special warning shouldn’t be checked by the compiler).

I tried to avoid renaming: https://github.com/apple/swift/pull/7823/ but then https://github.com/apple/swift/pull/9390\.

···

On Oct 24, 2017, at 5:10 AM, Tino <2th@gmx.de> wrote:

- Tino

"Flattening" in the context of functional programming (and that's where map and flatMap come from) means something like this:

F<F<T>> -> F<T>

So for example:

Array<Array<T>> -> Array<T>

or

Optional<Optional<T>> -> Optional<T>

but NOT:

Array<Optional<T>> -> Array<T>

It's simply the wrong word for the operation.

If "Map" is something like this:

(F<T> + (T -> U)) -> F<U>

then "FlatMap" means "map to the same functor, then flatten", thus:

(F<A> + (A -> F<B>)) -> F<F<B>>
then
(F<F<B>> -> F<B>)

In this case "U == F<B>"

In fact, there's (rightfully) a FlatMap method on Optional in cases in which the mapping function produces another Optional (notice that the chained unwrapping with "?." is a FlatMap operation).

Calling FlatMap something that is not (in the literature and community at large) was not a good idea, in my opinion.

Elviro

···

Il giorno 24 ott 2017, alle ore 14:10, Tino <2th@gmx.de> ha scritto:

Calling "flatMap" a map + filtering out the nil values was a TERRIBLE idea.

maybe I’m the one who’s getting this all wrong, but afaik the purpose of flatMap isn’t filtering out nil at all, but just a combination of map & flatten…
Optional<T> is like Array<T> with a maximal capacity of one (so it can either contain a single object, or it’s empty), and in this interpretation, there’s nothing wrong with the behavior.

Instead, I think Nobuo Saito is right, and that a renaming to filteredMap is only fighting symptoms, and not the cause (but I’m not that sure if there’s a good way to tackle the cause).
Besides that, all those alternative names also have potential for confusion:
Imho it isn’t intuitive what is filtered out — and don’t forget that the result of flatMap can contain nil elements…

If the biggest problem of flatMap is that people who don’t understand it write code that isn’t optimal (but still does the right thing!), I don’t think there is any change needed. I’m not even sure that that wrapping/unwrapping is actually done, because it should be discoverable by the compiler.

It would be nice if there was an way to warn in places where flatMap could be replaced with map, though (but imho this special warning shouldn’t be checked by the compiler).

- Tino

+1 good idea.

Re. the naming I would suggest `mapFilterNil` since it says what it does
and filter, nil, and map are all understood already in Swift. (I have
sympathy for people wanting `mapFilteringNil`, but Swift chose `filter`.)

The problems I see with `filterMap` are that:

  1. It sounds like it is a merged `filter` and `map` and therefore you
would expect it to have two arguments, one to filter and one to map, i.e.
`filterMap<R>(filter: (T) -> Bool, map: (T) -> R) -> [R]`.
  2. It sounds like it will filter the incoming values (for `nil`, but see
1 above) and then map, i.e. `filterMap<R>(map: (T?) -> R) -> [R]`, note
`T?` *not* `R?`.

  -- Howard.

···

On 24 October 2017 at 11:56, BJ Homer via swift-evolution < swift-evolution@swift.org> wrote:

I agree with Xiaodi; I like ‘filterMap’ more than ‘filteredMap’. But both
are superior to ‘flatMap’ in this context.

-BJ

On Oct 23, 2017, at 5:22 PM, Max Moiseev <moiseev@apple.com> wrote:

It occurred to me that filteringMap(_:) should be even more descriptive,
still conform to the guidelines, although similarly unprecedented and
un-googlable.

Max

On Oct 23, 2017, at 3:52 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

+1 in general. As to the name: since 'map' is used as a term of art,
'filterMap' seems superior to 'filteredMap', which half follows naming
guidelines and half is a term of art; neither is immediately comprehensible
but 'filterMap' can be googled and has precedents in other languages.
On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution < > swift-evolution@swift.org> wrote:

I strongly agree! In fact, I just started writing up a similar proposal
the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not
particularly descriptive. It’s certainly not obvious to newcomers that
‘flatMap’ will filter out results. And it’s not true to the existing usage
of ‘flatMap' from other languages; you have to really squint at it to see
how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution < >> swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this
functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at moiseev’s gists · GitHub
2f36376c8ef4c2b1273cff0bfd9c3b95 and is included below for your
convenience.

Max

Introduce Sequence.filteredMap(_:)

   - Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
   - Authors: Max Moiseev <https://github.com/moiseev&gt;
   - Review Manager: TBD
   - Status: Awaiting implementation

<0000-introduce-filteredmap.md · GitHub;
Introduction

We propose to deprecate the controversial version of a Sequence.flatMap method
and provide the same functionality under a different, and potentially more
descriptive, name.

<0000-introduce-filteredmap.md · GitHub;
Motivation

The Swift standard library currently defines 3 distinct overloads for
flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : SequenceOptional.flatMap<U>(_: (Wrapped) -> U?) -> U?Sequence.flatMap<U>(_: (Element) -> U?) -> [U]

The last one, despite being useful in certain situations, can be (and
often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}
func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}

What happens inside getNames is: thanks to the implicit promotion to
Optional, the result of the closure gets wrapped into a .some, then
immediately unwrapped by the implementation of flatMap, and appended to
the result array. All this unnecessary wrapping and unwrapping can be
easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}

It gets even worse when we consider future code modifications, like the
one where Swift 4 introduced a Stringconformance to the Collection protocol.
The following code used to compile (due to the flatMap overload in
question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}

But it no longer does, because now there is a better overload that does
not involve implicit promotion. In this particular case, the compiler error
would be obvious, as it would point at the same line where flatMap is
used. Imagine however if it was just a let names = people.flatMap { $
0.name } statement, and the names variable were used elsewhere. The
compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub
solution

We propose to deprecate the controversial overload of flatMap and
re-introduce the same functionality under a new name. The name being
filteredMap(_:) as we believe it best describes the intent of this
function.

For reference, here are the alternative names from other languages:

   - Haskell, Idris mapMaybe :: (a -> Maybe b) -> [a] -> [b]
   - Ocaml (Core and Batteries) filter_map : 'a t -> f:('a -> 'b
   option) -> 'b t
   - F# List.choose : ('T -> 'U option) -> 'T list -> 'U list
   - Rust fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> where
   F: FnMut(Self::Item) -> Option<B>
   - Scala def collect[B](pf: PartialFunction[A, B]): List[B]

<0000-introduce-filteredmap.md · GitHub
compatibility

Since the old function will still be available (although deprecated) all
the existing code will compile, producing a deprecation warning and a
fix-it.

<0000-introduce-filteredmap.md · GitHub
on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub
on API resilience

Ideally, the deprecated flatMap overload would not exist at the time
when ABI stability is declared, but in the worst case, it will be available
in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub
considered

It was attempted in the past to warn about this kind of misuse and do the
right thing instead by means of a deprecated overload with a
non-optional-returning closure. The attempt failed due to another implicit
promotion (this time to Any).

The following alternative names for this function were considered:

   - mapNonNil(_:) Does not communicate what happens to nil’s
   - mapSome(_:) Reads more like «map some elements of the sequence,
   but not the others» rather than «process only the ones that produce an
   Optional.some»
   - filterMap(_:) Does not really follow the naming guidelines and
   doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

If the biggest problem of flatMap is that people who don’t understand it write code that isn’t optimal (but still does the right thing!), I don’t think there is any change needed. I’m not even sure that that wrapping/unwrapping is actually done, because it should be discoverable by the compiler.

It can be seen that the generated SIL is more complicated for a flatMap case, the microbenchmark also shows that it’s about 3x slower than the map. But even if that can be optimized away, don’t you find a String example from the proposal even a little convincing?

It’s unfortunate when a piece of code gets a different meaning — but as you said:
It’s a misuse, and when you write code that is wrong, the results might be wrong as well.

Maybe Scala chose better name for the concept, but the Swift definition is consistent as it is now.
So, if you remove the third overload, the second one imho should be renamed, too.

Well, in Swift as in other languages, to filter means to keep the stuff
that matches the predicate. So it's not "filter nil" but "filter some." But
actually, it's not a filter; it's a flat map operation, since the optional
is being unwrapped.

Also, based on the precedent of "flat map," I think it really should be
"filter map" and not "map filter," which would suggest incorrectly that the
predicate would be for filtering.

···

On Mon, Oct 23, 2017 at 22:38 Howard Lovatt via swift-evolution < swift-evolution@swift.org> wrote:

+1 good idea.

Re. the naming I would suggest `mapFilterNil` since it says what it does
and filter, nil, and map are all understood already in Swift. (I have
sympathy for people wanting `mapFilteringNil`, but Swift chose `filter`.)

The problems I see with `filterMap` are that:

  1. It sounds like it is a merged `filter` and `map` and therefore you
would expect it to have two arguments, one to filter and one to map, i.e.
`filterMap<R>(filter: (T) -> Bool, map: (T) -> R) -> [R]`.
  2. It sounds like it will filter the incoming values (for `nil`, but see
1 above) and then map, i.e. `filterMap<R>(map: (T?) -> R) -> [R]`, note
`T?` *not* `R?`.

  -- Howard.

On 24 October 2017 at 11:56, BJ Homer via swift-evolution < > swift-evolution@swift.org> wrote:

I agree with Xiaodi; I like ‘filterMap’ more than ‘filteredMap’. But both
are superior to ‘flatMap’ in this context.

-BJ

On Oct 23, 2017, at 5:22 PM, Max Moiseev <moiseev@apple.com> wrote:

It occurred to me that filteringMap(_:) should be even more descriptive,
still conform to the guidelines, although similarly unprecedented and
un-googlable.

Max

On Oct 23, 2017, at 3:52 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

+1 in general. As to the name: since 'map' is used as a term of art,
'filterMap' seems superior to 'filteredMap', which half follows naming
guidelines and half is a term of art; neither is immediately comprehensible
but 'filterMap' can be googled and has precedents in other languages.
On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution < >> swift-evolution@swift.org> wrote:

I strongly agree! In fact, I just started writing up a similar proposal
the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not
particularly descriptive. It’s certainly not obvious to newcomers that
‘flatMap’ will filter out results. And it’s not true to the existing usage
of ‘flatMap' from other languages; you have to really squint at it to see
how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution < >>> swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this
functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at
0000-introduce-filteredmap.md · GitHub and is
included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

   - Proposal: SE-NNNN
   <https://gist.github.com/moiseev/NNNN-filename.md&gt;
   - Authors: Max Moiseev <https://github.com/moiseev&gt;
   - Review Manager: TBD
   - Status: Awaiting implementation

<0000-introduce-filteredmap.md · GitHub;
Introduction

We propose to deprecate the controversial version of a Sequence.flatMap method
and provide the same functionality under a different, and potentially more
descriptive, name.

<0000-introduce-filteredmap.md · GitHub;
Motivation

The Swift standard library currently defines 3 distinct overloads for
flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : SequenceOptional.flatMap<U>(_: (Wrapped) -> U?) -> U?Sequence.flatMap<U>(_: (Element) -> U?) -> [U]

The last one, despite being useful in certain situations, can be (and
often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}
func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}

What happens inside getNames is: thanks to the implicit promotion to
Optional, the result of the closure gets wrapped into a .some, then
immediately unwrapped by the implementation of flatMap, and appended to
the result array. All this unnecessary wrapping and unwrapping can be
easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}

It gets even worse when we consider future code modifications, like the
one where Swift 4 introduced a Stringconformance to the Collection protocol.
The following code used to compile (due to the flatMap overload in
question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}

But it no longer does, because now there is a better overload that does
not involve implicit promotion. In this particular case, the compiler error
would be obvious, as it would point at the same line where flatMap is
used. Imagine however if it was just a let names = people.flatMap { $
0.name } statement, and the names variable were used elsewhere. The
compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub
solution

We propose to deprecate the controversial overload of flatMap and
re-introduce the same functionality under a new name. The name being
filteredMap(_:) as we believe it best describes the intent of this
function.

For reference, here are the alternative names from other languages:

   - Haskell, Idris mapMaybe :: (a -> Maybe b) -> [a] -> [b]
   - Ocaml (Core and Batteries) filter_map : 'a t -> f:('a -> 'b
   option) -> 'b t
   - F# List.choose : ('T -> 'U option) -> 'T list -> 'U list
   - Rust fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> where
   F: FnMut(Self::Item) -> Option<B>
   - Scala def collect[B](pf: PartialFunction[A, B]): List[B]

<0000-introduce-filteredmap.md · GitHub
compatibility

Since the old function will still be available (although deprecated) all
the existing code will compile, producing a deprecation warning and a
fix-it.

<0000-introduce-filteredmap.md · GitHub
on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub
on API resilience

Ideally, the deprecated flatMap overload would not exist at the time
when ABI stability is declared, but in the worst case, it will be available
in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub
considered

It was attempted in the past to warn about this kind of misuse and do
the right thing instead by means of a deprecated overload with a
non-optional-returning closure. The attempt failed due to another implicit
promotion (this time to Any).

The following alternative names for this function were considered:

   - mapNonNil(_:) Does not communicate what happens to nil’s
   - mapSome(_:) Reads more like «map some elements of the sequence,
   but not the others» rather than «process only the ones that produce an
   Optional.some»
   - filterMap(_:) Does not really follow the naming guidelines and
   doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

Well, I have no problems with `flatMap` but then I am used to Monads.
I would not rename that overload if there is any hope that we might get Monads sometime in the future but probably there is none (which is sad).

So I would suggest `mapDroppingNil` or maybe `mapRejectingNil`.

-Thorsten

···

Am 24.10.2017 um 05:38 schrieb Howard Lovatt via swift-evolution <swift-evolution@swift.org>:

+1 good idea.

Re. the naming I would suggest `mapFilterNil` since it says what it does and filter, nil, and map are all understood already in Swift. (I have sympathy for people wanting `mapFilteringNil`, but Swift chose `filter`.)

The problems I see with `filterMap` are that:

  1. It sounds like it is a merged `filter` and `map` and therefore you would expect it to have two arguments, one to filter and one to map, i.e. `filterMap<R>(filter: (T) -> Bool, map: (T) -> R) -> [R]`.
  2. It sounds like it will filter the incoming values (for `nil`, but see 1 above) and then map, i.e. `filterMap<R>(map: (T?) -> R) -> [R]`, note `T?` *not* `R?`.

  -- Howard.

On 24 October 2017 at 11:56, BJ Homer via swift-evolution <swift-evolution@swift.org> wrote:
I agree with Xiaodi; I like ‘filterMap’ more than ‘filteredMap’. But both are superior to ‘flatMap’ in this context.

-BJ

On Oct 23, 2017, at 5:22 PM, Max Moiseev <moiseev@apple.com> wrote:

It occurred to me that filteringMap(_:) should be even more descriptive, still conform to the guidelines, although similarly unprecedented and un-googlable.

Max

On Oct 23, 2017, at 3:52 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

+1 in general. As to the name: since 'map' is used as a term of art, 'filterMap' seems superior to 'filteredMap', which half follows naming guidelines and half is a term of art; neither is immediately comprehensible but 'filterMap' can be googled and has precedents in other languages.
On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution <swift-evolution@swift.org> wrote:

I strongly agree! In fact, I just started writing up a similar proposal the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will filter out results. And it’s not true to the existing usage of ‘flatMap' from other languages; you have to really squint at it to see how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)
Proposal: SE-NNNN
Authors: Max Moiseev
Review Manager: TBD
Status: Awaiting implementation
Introduction

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

Motivation

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name } statement, and the names variable were used elsewhere. The compiler error would be misleading.

Proposed solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris >>>>>> mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) >>>>>> filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# >>>>>> List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust >>>>>> fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> >>>>>> where F: FnMut(Self::Item) -> Option<B>
Scala >>>>>> def collect[B](pf: PartialFunction[A, B]): List[B]
Source compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

Effect on ABI stability

This is an additive API change, and does not affect ABI stability.

Effect on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

Alternatives considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) >>>>>> Does not communicate what happens to nil’s
mapSome(_:) >>>>>> Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) >>>>>> Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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

I also cast my vote for filterMap. It’s concise, understandable and is kind of a term of art also.

+1. This seems like the best option to me.

···

On Oct 24, 2017, at 1:57 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

On 24 Oct 2017, at 02:56, BJ Homer via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I agree with Xiaodi; I like ‘filterMap’ more than ‘filteredMap’. But both are superior to ‘flatMap’ in this context.

-BJ

On Oct 23, 2017, at 5:22 PM, Max Moiseev <moiseev@apple.com <mailto:moiseev@apple.com>> wrote:

It occurred to me that filteringMap(_:) should be even more descriptive, still conform to the guidelines, although similarly unprecedented and un-googlable.

Max

On Oct 23, 2017, at 3:52 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

+1 in general. As to the name: since 'map' is used as a term of art, 'filterMap' seems superior to 'filteredMap', which half follows naming guidelines and half is a term of art; neither is immediately comprehensible but 'filterMap' can be googled and has precedents in other languages.
On Mon, Oct 23, 2017 at 17:24 BJ Homer via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I strongly agree! In fact, I just started writing up a similar proposal the other day, but hadn’t had time to finish it yet.

The current name for this particular filtering variant is not particularly descriptive. It’s certainly not obvious to newcomers that ‘flatMap’ will filter out results. And it’s not true to the existing usage of ‘flatMap' from other languages; you have to really squint at it to see how any “flattening” is happening at all.

So yes, a big +1 from me. Thanks!

-BJ Homer

On Oct 23, 2017, at 4:15 PM, Max Moiseev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi swift-evolution!

I would like to propose the following change to the standard library:

deprecate `Sequence.flatMap<U>(_: (Element) -> U?) -> [U]` and make this functionality available under a new name `Sequence.filteredMap(_:)`.

The draft is available at 0000-introduce-filteredmap.md · GitHub and is included below for your convenience.

Max

Introduce Sequence.filteredMap(_:)

Proposal: SE-NNNN <https://gist.github.com/moiseev/NNNN-filename.md&gt;
Authors: Max Moiseev <https://github.com/moiseev&gt;
Review Manager: TBD
Status: Awaiting implementation
<0000-introduce-filteredmap.md · GitHub

We propose to deprecate the controversial version of a Sequence.flatMap method and provide the same functionality under a different, and potentially more descriptive, name.

<0000-introduce-filteredmap.md · GitHub

The Swift standard library currently defines 3 distinct overloads for flatMap:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
    where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last one, despite being useful in certain situations, can be (and often is) misused. Consider the following snippet:

struct Person {
  var age: Int
  var name: String
}

func getAges(people: [Person]) -> [Int] {
  return people.flatMap { $0.age }
}
What happens inside getNames is: thanks to the implicit promotion to Optional, the result of the closure gets wrapped into a .some, then immediately unwrapped by the implementation of flatMap, and appended to the result array. All this unnecessary wrapping and unwrapping can be easily avoided by just using map instead.

func getAges(people: [Person]) -> [Int] {
  return people.map { $0.age }
}
It gets even worse when we consider future code modifications, like the one where Swift 4 introduced a Stringconformance to the Collection protocol. The following code used to compile (due to the flatMap overload in question).

func getNames(people: [Person]) -> [String] {
  return people.flatMap { $0.name }
}
But it no longer does, because now there is a better overload that does not involve implicit promotion. In this particular case, the compiler error would be obvious, as it would point at the same line where flatMap is used. Imagine however if it was just a let names = people.flatMap { $0.name <http://0.name/&gt; } statement, and the names variable were used elsewhere. The compiler error would be misleading.

<0000-introduce-filteredmap.md · GitHub solution

We propose to deprecate the controversial overload of flatMap and re-introduce the same functionality under a new name. The name being filteredMap(_:) as we believe it best describes the intent of this function.

For reference, here are the alternative names from other languages:

Haskell, Idris >>>>> mapMaybe :: (a -> Maybe b) -> [a] -> [b]
Ocaml (Core and Batteries) >>>>> filter_map : 'a t -> f:('a -> 'b option) -> 'b t
F# >>>>> List.choose : ('T -> 'U option) -> 'T list -> 'U list
Rust >>>>> fn filter_map<B, F>(self, f: F) -> FilterMap<Self, F> >>>>> where F: FnMut(Self::Item) -> Option<B>
Scala >>>>> def collect[B](pf: PartialFunction[A, B]): List[B]
<0000-introduce-filteredmap.md · GitHub compatibility

Since the old function will still be available (although deprecated) all the existing code will compile, producing a deprecation warning and a fix-it.

<0000-introduce-filteredmap.md · GitHub on ABI stability

This is an additive API change, and does not affect ABI stability.

<0000-introduce-filteredmap.md · GitHub on API resilience

Ideally, the deprecated flatMap overload would not exist at the time when ABI stability is declared, but in the worst case, it will be available in a deprecated form from a library post-ABI stability.

<0000-introduce-filteredmap.md · GitHub considered

It was attempted in the past to warn about this kind of misuse and do the right thing instead by means of a deprecated overload with a non-optional-returning closure. The attempt failed due to another implicit promotion (this time to Any).

The following alternative names for this function were considered:

mapNonNil(_:) >>>>> Does not communicate what happens to nil’s
mapSome(_:) >>>>> Reads more like «map some elements of the sequence, but not the others» rather than «process only the ones that produce an Optional.some»
filterMap(_:) >>>>> Does not really follow the naming guidelines and doesn’t seem to be common enough to be considered a term of art.

_______________________________________________
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 <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