3

Combine seems to be built with type-erasure in mind. One reason is to avoid the complex types that are generated by many chained operators, such as the explanation defined in this question.

I'm curious about the cases where you would not want to call eraseToAnyPublisher(). I thought of a possible candidate:

func fetchResource() -> Future<Model, Error>

In this case, fetchResource isn't meant to emit more than once, and giving the return type of Future would add clarity to the functionality.

You could also return AnyPublisher:

func fetchResource() -> AnyPublisher<Model, Error>

This allows you to hide the implementation details from the consumer and protect against misuse. There is a tradeoff though... the consumer wouldn't know the semantics of the Future:

  • Future executes as soon as it's created, compared to some publishers that emit values only when there's a subscription
  • Future retains their eventual result and shares/replays the value to any future subscribers

Anyone know of any good examples of when you wouldn't eraseToAnyPublisher()?

Kelvin Lau
  • 6,373
  • 6
  • 34
  • 57
  • 1
    `eraseToAnyPublisher`/`AnyPublisher` is meant to solve the problem of not being able to use `Publisher` as a type of a variable/property/parameter/method return type, and it's not like there's any alternatives to `eraseToAnyPublisher`... So the answer is just "when you don't need it". – Sweeper Oct 10 '20 at 06:31
  • What about the benefits of type erasure? Are there specific scenarios where the benefits of protection against misuse is outweighed by the importance of showing semantics? (Such as Future) – Kelvin Lau Oct 10 '20 at 06:40
  • 1
    By the way, `dataTaskPublisher` returns a `URLSession.DataTaskPublisher`... – Sweeper Oct 10 '20 at 06:42
  • Oops, I made an assumption there... will fix – Kelvin Lau Oct 10 '20 at 06:43
  • 2
    "Are there specific scenarios...?" I think this is getting into opinion-based territory, but in my experience, it's almost never worth it to declare a method to return a specific kind of publisher. What if your `fetchResource` needs to change in the future (no pun intended)? Let's say it now needs to map its output a little bit before returning. Its return type now needs to be `Publishers.Map, Model>`. The callers depend on it returning a `Future` will now break. – Sweeper Oct 10 '20 at 06:54

1 Answers1

3
  1. AnyPublisher is only a temporary solution until we're able to add constraints to opaque types.

e.g. this…

var publisher: AnyPublisher<Int, Never> { .init(Just(1)) }

…should actually be something like this:

var publisher<Publisher: Combine.Publisher>: some Publisher
where Publisher.Output == Int, Publisher.Failure == Never {
  Just(1)
}

You'll find a lot of discussion on the Swift forum about how this is not easy to implement (and what syntax to use!), hence why we're still using the intermediate solution of public type-erasing types.


  1. There's no protocol in between Future and Publisher. That's what you're looking for, with this question. If you'd like to enforce a stronger contract, add some stuff to an inherited protocol…
protocol Futurey: Publisher {
extension Future: Futurey {

…and then, unfortunately, you'll have to create another erasing type. For now.

struct AnyFuturey<Output, Failure> {