Swift 4 Strings and flatMap

Hi Swift users,

I just ran into a bug after a Swift 4 migration where we expected [String]
and got [Character] from a flatMap since flatMap will flatten the String
since it's now a collection. While I understand why flatMap behaves this
way now that string are collections, in testing this I found some weird
behaviour...

var strArr = ["Hi", "hello"]

let result = strArr.flatMap { x in

    return x

}

The type of results ends up being [Character] in the above case. However,
adding a print statement changes things.

var strArr = ["Hi", "hello"]

let result = strArr.flatMap { x in

    print(x)

    return x

}

In this case, result is of type [String]

This seems like a bug, or is this expected Swift behaviour?

Thanks,

Santiago

It looks like the compiler infers the type of the closure in the second example to be (String) -> Optional<String> instead of String -> String.

I don't know why and I definitely find it surprising.

···

On 21.10.17 02:50, Santiago Gil via swift-users wrote:

I just ran into a bug after a Swift 4 migration where we expected [String] and got [Character] from a flatMap since flatMap will flatten the String since it's now a collection. While I understand why flatMap behaves this way now that string are collections, in testing this I found some weird behaviour...

var strArr = ["Hi", "hello"]

let result = strArr.flatMap { x in

returnx

}

The type of results ends up being [Character] in the above case. However, adding a print statement changes things.

var strArr = ["Hi", "hello"]

let result = strArr.flatMap { x in

print(x)

return x

}

In this case, result is of type [String]

This seems like a bug, or is this expected Swift behaviour?

Compare [SR-1570] 'Generic parameter 'Result' could not be inferred' compiler error for wrapper functions like autoreleasepool that return the result of their closure argument. · Issue #44179 · apple/swift · GitHub : Swift can infer the return type from single-statement closures, but not from multi-statement closures. Therefore in

    let result = strArr.flatMap { x in
        return x
    }

the closure type is inferred as (String)-> String, and therefore the flatMap call matches exactly the

   func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element]
   where SegmentOfResult : Sequence

Sequence method, with Self.Element == String and SegmentOfResult == String, and flatMap returns [SegmentOfResult.Element] == [Character].

In

    let result = strArr.flatMap { x in
        print(x)
        return x
    }

the closure return type is not inferred from the closure itself, because it is a multi-statement closure. Here the compiler (apparently) chooses the

    public func flatMap(_ transform: (Element) throws -> String?) rethrows -> [String]
    
Array method, and promotes `String` to `String?`. You would get the same result with an explicit return type in the single-statement closure:

    let result = strArr.flatMap { x -> String? in
        return x
    }

But why do you use flatMap at all? If the intention is to map [String] to [String] then map() would be the right method:

    let strArr = ["Hi", "hello"]
    let result: [String] = strArr.map { x in
        return x
    }
    print(result) // ["Hi", "hello"]

Regards, Martin

···

On 21. Oct 2017, at 02:50, Santiago Gil via swift-users <swift-users@swift.org> wrote:

Hi Swift users,

I just ran into a bug after a Swift 4 migration where we expected [String] and got [Character] from a flatMap since flatMap will flatten the String since it's now a collection. While I understand why flatMap behaves this way now that string are collections, in testing this I found some weird behaviour...

var strArr = ["Hi", "hello"]

let result = strArr.flatMap { x in

    return x

}

The type of results ends up being [Character] in the above case. However, adding a print statement changes things.

var strArr = ["Hi", "hello"]

let result = strArr.flatMap { x in

    print(x)

    return x

}

In this case, result is of type [String]

This seems like a bug, or is this expected Swift behaviour?

Thanks,

Santiago

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

The object we were using flatMap on used to be optional. The fix was to use
a map instead since it was no longer needed to be flatMapped. I agree that
this would be a mis-use of flatMap but the inference behaviour here is
still unexpected.

If the compiler doesn't support multi-statement closure type inference it
would be nice for it to warn or error out in cases like this to enforce
type, or it can also be handled by a linter/static analysis.

- Santiago

···

On Sat, Oct 21, 2017 at 12:39 PM Martin R <martinr448@gmail.com> wrote:

Compare [SR-1570] 'Generic parameter 'Result' could not be inferred' compiler error for wrapper functions like autoreleasepool that return the result of their closure argument. · Issue #44179 · apple/swift · GitHub : Swift can infer the
return type from single-statement closures, but not from multi-statement
closures. Therefore in

    let result = strArr.flatMap { x in
        return x
    }

the closure type is inferred as (String)-> String, and therefore the
flatMap call matches exactly the

   func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws ->
SegmentOfResult) rethrows -> [SegmentOfResult.Element]
   where SegmentOfResult : Sequence

Sequence method, with Self.Element == String and SegmentOfResult ==
String, and flatMap returns [SegmentOfResult.Element] == [Character].

In

    let result = strArr.flatMap { x in
        print(x)
        return x
    }

the closure return type is not inferred from the closure itself, because
it is a multi-statement closure. Here the compiler (apparently) chooses the

    public func flatMap(_ transform: (Element) throws -> String?) rethrows
-> [String]

Array method, and promotes `String` to `String?`. You would get the same
result with an explicit return type in the single-statement closure:

    let result = strArr.flatMap { x -> String? in
        return x
    }

But why do you use flatMap at all? If the intention is to map [String] to
[String] then map() would be the right method:

    let strArr = ["Hi", "hello"]
    let result: [String] = strArr.map { x in
        return x
    }
    print(result) // ["Hi", "hello"]

Regards, Martin

> On 21. Oct 2017, at 02:50, Santiago Gil via swift-users < > swift-users@swift.org> wrote:
>
> Hi Swift users,
>
> I just ran into a bug after a Swift 4 migration where we expected
[String] and got [Character] from a flatMap since flatMap will flatten the
String since it's now a collection. While I understand why flatMap behaves
this way now that string are collections, in testing this I found some
weird behaviour...
>
> var strArr = ["Hi", "hello"]
>
> let result = strArr.flatMap { x in
>
> return x
>
> }
>
>
> The type of results ends up being [Character] in the above case.
However, adding a print statement changes things.
>
>
> var strArr = ["Hi", "hello"]
>
> let result = strArr.flatMap { x in
>
> print(x)
>
> return x
>
> }
>
> In this case, result is of type [String]
>
> This seems like a bug, or is this expected Swift behaviour?
>
> Thanks,
>
> Santiago
>
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org
> https://lists.swift.org/mailman/listinfo/swift-users