Rational behind having 2 different behaviors for flatMap?


(Maxim Veksler) #1

Hi everyone,

I've discovered today that Swift will actually choose 2 very differently
behaving types of flatMap implementation based on the input signature.

For a Sequence of options it will call a flatMap that filters out nil's.
For a Sequence of Sequence's it will call a flattening function, without
filtering.

Leading to code that (IMHO) reads very not inconsistency, and unexpected.
Sometime even looking a bit funny such as collection.flatMap.flatMap:

  5> let deep = [["1989", nil], [nil, "Red"], [nil, nil]]
deep: [[String?]] = 3 values {
  [0] = 2 values {
    [0] = "1989"
    [1] = nil
  }
  [1] = 2 values {
    [0] = nil
    [1] = "Red"
  }
  [2] = 2 values {
    [0] = nil
    [1] = nil
  }
}
  6> deep.flatMap { $0 }
$R1: [String?] = 6 values {
  [0] = "1989"
  [1] = nil
  [2] = nil
  [3] = "Red"
  [4] = nil
  [5] = nil
}
  7> deep.flatMap { $0 }.flatMap { $0 }
$R2: [String] = 2 values {
  [0] = "1989"
  [1] = "Red"
}

I wonder why it was implemented this way?


(Saagar Jha) #2

flatMap was designed to work this way. How I rationalize it is that flatMap “extracts” the value from an array’s elements and expands it. For an Array, this is just taking out the individual Elements, but for an Optional, which a “wrapper” around one value, it just takes this value out. Optionals with no value (the .none case, or nil as it’s more commonly known) have nothing to contribute and thus are filtered out.

Saagar Jha

P.S. You can call flatMap without a closure: deep.flatMap().flatMap()

···

On Feb 13, 2017, at 4:31 PM, Maxim Veksler via swift-users <swift-users@swift.org> wrote:

Hi everyone,

I've discovered today that Swift will actually choose 2 very differently behaving types of flatMap implementation based on the input signature.

For a Sequence of options it will call a flatMap that filters out nil's. For a Sequence of Sequence's it will call a flattening function, without filtering.

Leading to code that (IMHO) reads very not inconsistency, and unexpected. Sometime even looking a bit funny such as collection.flatMap.flatMap:

  5> let deep = [["1989", nil], [nil, "Red"], [nil, nil]]
deep: [[String?]] = 3 values {
  [0] = 2 values {
    [0] = "1989"
    [1] = nil
  }
  [1] = 2 values {
    [0] = nil
    [1] = "Red"
  }
  [2] = 2 values {
    [0] = nil
    [1] = nil
  }
}
  6> deep.flatMap { $0 }
$R1: [String?] = 6 values {
  [0] = "1989"
  [1] = nil
  [2] = nil
  [3] = "Red"
  [4] = nil
  [5] = nil
}
  7> deep.flatMap { $0 }.flatMap { $0 }
$R2: [String] = 2 values {
  [0] = "1989"
  [1] = "Red"
}
I wonder why it was implemented this way?

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


(Saagar Jha) #3

Hmm, it looks like you’re right. I could swear I’ve used flatMap without a closure before…oh well, looks like that’s what happens when you send code without running it in a Playground.

Saagar Jha

···

On Feb 13, 2017, at 5:33 PM, Jon Shier <jon@jonshier.com> wrote:

According to the compiler, the closure argument for flatMap is not optional or defaulted, so it’s always required. I think it should default to { $0 }, but whatever.

Jon

On Feb 13, 2017, at 8:26 PM, Saagar Jha via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

flatMap was designed to work this way. How I rationalize it is that flatMap “extracts” the value from an array’s elements and expands it. For an Array, this is just taking out the individual Elements, but for an Optional, which a “wrapper” around one value, it just takes this value out. Optionals with no value (the .none case, or nil as it’s more commonly known) have nothing to contribute and thus are filtered out.

Saagar Jha

P.S. You can call flatMap without a closure: deep.flatMap().flatMap()

On Feb 13, 2017, at 4:31 PM, Maxim Veksler via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi everyone,

I've discovered today that Swift will actually choose 2 very differently behaving types of flatMap implementation based on the input signature.

For a Sequence of options it will call a flatMap that filters out nil's. For a Sequence of Sequence's it will call a flattening function, without filtering.

Leading to code that (IMHO) reads very not inconsistency, and unexpected. Sometime even looking a bit funny such as collection.flatMap.flatMap:

  5> let deep = [["1989", nil], [nil, "Red"], [nil, nil]]
deep: [[String?]] = 3 values {
  [0] = 2 values {
    [0] = "1989"
    [1] = nil
  }
  [1] = 2 values {
    [0] = nil
    [1] = "Red"
  }
  [2] = 2 values {
    [0] = nil
    [1] = nil
  }
}
  6> deep.flatMap { $0 }
$R1: [String?] = 6 values {
  [0] = "1989"
  [1] = nil
  [2] = nil
  [3] = "Red"
  [4] = nil
  [5] = nil
}
  7> deep.flatMap { $0 }.flatMap { $0 }
$R2: [String] = 2 values {
  [0] = "1989"
  [1] = "Red"
}
I wonder why it was implemented this way?

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

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


(Jon Shier) #4

According to the compiler, the closure argument for flatMap is not optional or defaulted, so it’s always required. I think it should default to { $0 }, but whatever.

Jon

···

On Feb 13, 2017, at 8:26 PM, Saagar Jha via swift-users <swift-users@swift.org> wrote:

flatMap was designed to work this way. How I rationalize it is that flatMap “extracts” the value from an array’s elements and expands it. For an Array, this is just taking out the individual Elements, but for an Optional, which a “wrapper” around one value, it just takes this value out. Optionals with no value (the .none case, or nil as it’s more commonly known) have nothing to contribute and thus are filtered out.

Saagar Jha

P.S. You can call flatMap without a closure: deep.flatMap().flatMap()

On Feb 13, 2017, at 4:31 PM, Maxim Veksler via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi everyone,

I've discovered today that Swift will actually choose 2 very differently behaving types of flatMap implementation based on the input signature.

For a Sequence of options it will call a flatMap that filters out nil's. For a Sequence of Sequence's it will call a flattening function, without filtering.

Leading to code that (IMHO) reads very not inconsistency, and unexpected. Sometime even looking a bit funny such as collection.flatMap.flatMap:

  5> let deep = [["1989", nil], [nil, "Red"], [nil, nil]]
deep: [[String?]] = 3 values {
  [0] = 2 values {
    [0] = "1989"
    [1] = nil
  }
  [1] = 2 values {
    [0] = nil
    [1] = "Red"
  }
  [2] = 2 values {
    [0] = nil
    [1] = nil
  }
}
  6> deep.flatMap { $0 }
$R1: [String?] = 6 values {
  [0] = "1989"
  [1] = nil
  [2] = nil
  [3] = "Red"
  [4] = nil
  [5] = nil
}
  7> deep.flatMap { $0 }.flatMap { $0 }
$R2: [String] = 2 values {
  [0] = "1989"
  [1] = "Red"
}
I wonder why it was implemented this way?

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

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