Unexpected behavior of `replaceNil(with:)`

Using code similar to the following

[1, nil, 2, 4].publisher.replaceNil(with: 0).eraseToAnyPublisher()

Will product a AnyPublisher<Int?, Never> instead of the expected AnyPublisher<Int, Never>.

This seems to be a bug or a miss β€” since if you have a guarantee that every nil value is replaced with a non-null value, you can safely return a non-optional Output type without us having to add a redundant compactMap { $0 }.

Would appreciate your feedback :)

Thanks,
Shai.

I don't know why it even exists to begin with (seems like clutter to me), but it is more code to keep the same level of optionality than it is to reduce itβ€”it saves that Optional initialization. :man_shrugging:

[1, nil, 2, 4].publisher.map { $0 ?? 0 }
  .eraseToAnyPublisher() as AnyPublisher<Int, Never>

[1, nil, 2, 4].publisher.map { $0 ?? Optional(0) }
  .eraseToAnyPublisher() as AnyPublisher<Int?, Never>

It's not clutter, it's declarative which is what operators are meant to do, but yes - it needs do be:

replaceNil(with: 7) = $0 ?? 7

1 Like

The issue appears to be with this overload of replaceNil(with:):

public func replaceNil<T>(with output: T) -> Publishers.Sequence<[Publishers.Sequence<Elements, Failure>.Output], Failure> where Elements.Element == T?

Note that the return type doesn't mention T anywhere. To me it seems like it should return Publishers.Sequence<[T], Failure>.

If you move the eraseToAnyPublisher() call before replaceNil(with:), it uses the more general overload and works:

[1, nil, 2, 4].publisher.eraseToAnyPublisher().replaceNil(with: 0) // Publishers.Map<AnyPublisher<Int?, Never>, Int>
2 Likes

Yup I forgot to mention the weird overload... This is a strange case for it anyways. I'm wondering if it would stay there forever in the sake of backward compatibility. Otherwise, making it return a Sequence publisher makes some sense, agreed.

Yeah, it's a weird overload. Fortunately, you can still do this:

([1, nil, 2, 4]
  .publisher
  .replaceNil(with: 0) as Publishers.Map
  )
  .eraseToAnyPublisher()

That's not too fortunate TBH, it makes even less sense using this instead of just map { $0 ?? 0 } since it's so verbose.

1 Like