Cannot convert return expression in flatMap with a meaningless expression inside
The flatMap(_:)
method on Sequence
currently (as of Swift 4) has two different meanings:
It can take a transform closure that returns an optional
T?
, and it will return a[T]
, filtering out thenil
results (this overload is to be renamed tocompactMap(_:)
in a future version).public func flatMap<ElementOfResult>(
_ transform: (Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult]It can take a transform closure that returns a
Sequence
, and it will return an array containing the concatenation of all the resulting sequences.public func flatMap<SegmentOfResult : Sequence>(
_ transform: (Element) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.Element]
Now, in Swift 4, String
became a RangeReplaceableCollection
(and therefore a Sequence
). So Swift 3 code that did this:
// returns ["foo"], as using the `nil` filtering flatMap, the elements in the closure
// are implicitly promoted to optional strings.
["foo"].flatMap { $0 }
now does this:
// returns ["f", "o", "o"], a [Character], as using the Sequence concatenation flatMap,
// as String is now a Sequence (compiler favours this overload as it avoids the implicit
// conversion from String to String?)
["foo"].flatMap { $0 }
To preserve source compatibility, specialised flatMap
overloads were added for strings:
//===----------------------------------------------------------------------===//
// The following overloads of flatMap are carefully crafted to allow the code
// like the following:
// ["hello"].flatMap { $0 }
// return an array of strings without any type context in Swift 3 mode, at the
// same time allowing the following code snippet to compile:
// [0, 1].flatMap { x in
// if String(x) == "foo" { return "bar" } else { return nil }
// }
// Note that the second overload is declared on a more specific protocol.
// See: test/stdlib/StringFlatMap.swift for tests.
extension Sequence {
@_inlineable // FIXME(sil-serialize-all)
@available(swift, obsoleted: 4)
public func flatMap(
_ transform: (Element) throws -> String
) rethrows -> [String] {
return try map(transform)
}
}
extension Collection {
@_inlineable // FIXME(sil-serialize-all)
public func flatMap(
_ transform: (Element) throws -> String?
) rethrows -> [String] {
return try _flatMap(transform)
}
}
Such that the above usage would still return a [String]
in Swift 3 compatibility mode, but a [Character]
in Swift 4.
So, why does
let array = [1, 2, 3, 4, 5, 6]
array
.flatMap {
print("DD")
return $0 // Cannot convert return expression of type 'Int' to return type 'String?'
}
.forEach {
print("SS")
print($0)
}
tell you that the closure should return a String?
?
Well, Swift currently doesn't infer parameter and return types for multi-statement closures (see this Q&A for more info). So the flatMap(_:)
overloads where the closure returns either a generic T?
or a generic S : Sequence
aren't eligible to be called without explicit type annotations, as they would require type inference to satisfy the generic placeholders.
Thus, the only overload that is eligible, is the special String
source compatibility one, so the compiler is expecting the closure to return a String?
.
To fix this, you can explicitly annotate the return type of the closure:
array
.flatMap { i -> Int? in
print("DD")
return i
}
.forEach {
print("SS")
print($0)
}
But if you're not actually using the optional filtering functionality of this flatMap(_:)
overload in your real code, you should use map(_:)
instead.
Using Java 8's Optional with Stream::flatMap
Java 9
Optional.stream
has been added to JDK 9. This enables you to do the following, without the need of any helper method:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(Optional::stream)
.findFirst();
Java 8
Yes, this was a small hole in the API, in that it's somewhat inconvenient to turn an Optional<T>
into a zero-or-one length Stream<T>
. You could do this:
Optional<Other> result =
things.stream()
.map(this::resolve)
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
.findFirst();
Having the ternary operator inside the flatMap
is a bit cumbersome, though, so it might be better to write a little helper function to do this:
/**
* Turns an Optional<T> into a Stream<T> of length zero or one depending upon
* whether a value is present.
*/
static <T> Stream<T> streamopt(Optional<T> opt) {
if (opt.isPresent())
return Stream.of(opt.get());
else
return Stream.empty();
}
Optional<Other> result =
things.stream()
.flatMap(t -> streamopt(resolve(t)))
.findFirst();
Here, I've inlined the call to resolve()
instead of having a separate map()
operation, but this is a matter of taste.
Java's flatMap on list of list of optional integers
It should be:
List<Integer> flattened =
list
.stream()
.filter (Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
Your flatMap
expects a function that transforms a Stream
element to a Stream
. You should use map
instead (to extract the value of the Optional
). In addition, you need to filter out empty Optional
s (unless you wish to transform them to null
s).
Without the filtering:
List<Integer> flattened =
list
.stream()
.map(o -> o.orElse(null))
.collect(Collectors.toList());
Combine weird compile error after placing a print statement in a flatMap operator closure
Multi-statement closures do not take part in type inference, so making the closure to flatMap a multi-statement one, you are somehow causing it to incorrectly infer the type parameters of scan
. You can provide the types it needs by writing them out in the closure:
.flatMap { storyIDs -> AnyPublisher<Story, API.Error> in
print("StoryIDs are \(storyIDs)")
return self.mergedStories(ids: storyIDs)
}
If you just want to print the values you receive, you can call .print()
as well.
Update:
I played around with this a bit more, and I found that if you put everything before scan
into a let
constant, and call scan
on that constant, the error moves to somewhere else:
let pub = URLSession.shared
.dataTaskPublisher(for: EndPoint.stories.url)
.map(\.data)
.decode(type: [Int].self, decoder: decoder) //<--- Error fires here now!
.mapError { error -> API.Error in
switch error {
case is URLError:
return Error.addressUnreachable(EndPoint.stories.url)
default:
return Error.invalidResponse
}
}
.filter { !$0.isEmpty }
.flatMap { storyIDs in
print("StoryIDs are \(storyIDs)")
return self.mergedStories(ids: storyIDs)
}
return pub.scan([]) { stories, story -> [Story] in
stories + [story]
}
.map { $0.sorted() }
.eraseToAnyPublisher()
Instance method 'decode(type:decoder:)' requires the types 'URLSession.DataTaskPublisher.Output' (aka '(data: Data, response: URLResponse)') and 'JSONDecoder.Input' (aka 'Data') be equivalent
This time, it caused decode
's type arguments to be inferred wrongly. The original error at scan
disappeared because now the compiler knows that pub
has a definite type, though it had (incorrectly) found an error somewhere else before it could determine the type of pub
.
Following this pattern, I made another temporary let
constant:
let pub1 = URLSession.shared
.dataTaskPublisher(for: EndPoint.stories.url)
.map(\.data)
let pub2 = pub1
.decode(type: [Int].self, decoder: decoder)
.mapError { error -> API.Error in
switch error {
case is URLError:
return Error.addressUnreachable(EndPoint.stories.url)
default:
return Error.invalidResponse
}
}
.filter { !$0.isEmpty }
.flatMap { storyIDs in
print("StoryIDs are \(storyIDs)")
return self.mergedStories(ids: storyIDs)
}
return pub2.scan([]) { stories, story -> [Story] in
stories + [story]
}
.map { $0.sorted() }
.eraseToAnyPublisher()
Finally, the compiler shows a useful message at storyIDs in
, which leads use to the solution at the start of the answer:
Unable to infer complex closure return type; add explicit type to disambiguate
It even tells us what type we should insert!
Swift reduce - why is value optional?
In order for you to get this result at all (as far as I can tell), the enclosing function must return an Int?
. The implication is that reduce
can return an optional. Absent the conditional, the compiler can determine that reduce
will never return nil, i.e., total
is never nil. So, the compiler infers that the return type of the closure is Int
. The compiler appears to be entangling type inferencing for the reduce
closure and total
. Once you add the conditional, the compiler is incapable of determining whether the reduce
will return nil or not. Now when it unnecessarily infers the type for total
it gets it wrong.
To me this looks like a case of Swift type inferencing gone astray. Clearly, total
is never nil based on the documentation of enumerated
.
If you modify the code slightly you get the expected result:
return first.enumerated().reduce(0) { (total: Int, letter) in
let index = first.index(first.startIndex, offsetBy: letter.offset)
if first[index] != second[index]{
return total + 1
}
return total
}
Swift makes a lot of type inferences and it is really great because I get strong typing while retaining many of the benefits of a dynamic language. In my experience, however, swift's inferences can be mystifying sometimes. It handles arcane situations with ease and stumbles over something I think is obvious.
It looks like a bug to me.
Swift 4.2 weird -.map- behavior
The way map
works in Swift (and elsewhere) is that it can map (change) input from one type to another. So in your case, you will call map
on your [CBPeripheral]
and for each element, the map
function will return another value. So, if the identifier
is a String
, you will end out with an array of Strings.
That is how it should work.
However :)
In your case, you have defined your mapping function like so
(perif) -> Void
Meaning that you will run through your array and call map, with a function that returns nothing. Meaning that the outcome will be an array filled with Void values.
Therefore, as others are suggesting in the comments, if you just want to examine the content of your cbPerifList
, you are better of using a for in
loop like so
for cbPerif in cbPerifList {
print(cbPerif.identifier)
}
If however, you're interested in getting an array of the names of your CBPeripherals
you can do something like this
let names = cbPerifList.map { $0.identifier }
A comment to the above: To read more about how we can end up just writing { $0.name }
have a look at the chapter about Closures in the Swift documentation, or this answer
Hope that helps you.
Related Topics
Check Password String Strength Criteria in Swift
Choosing Coredata Entities from Form Picker
How to Detect Which Skspritenode Has Been Touched
Iterate Over Collection Two At a Time in Swift
What Is _: in Swift Telling Me
Print Without Newline in Swift
Dispatch_Once After the Swift 3 Gcd API Changes
Providing a Default Value For an Optional in Swift
Get Integer Value from String in Swift
What Does a Module Mean in Swift
Swift - Iboutletcollection Equivalent
How to Update the Constant Height Constraint of a Uiview Programmatically
Swift Generic Coercion Misunderstanding