0

There are some cases when code should be executed but its result should be not handled.

If I use completion blocks the code looks like the following:

func someFunc(completion: (() -> ())?) { ... }

someFunc(nil)

Now if I use Combine I should write something like this:

func someFunc() -> AnyPublisher<(), Never> { ... }

someFunc()
.sink { _ in
} receiveValue: {
}
.store(in: ...)

But is it more convenient solution like a class which implements Subscriber protocol but does nothing?

Why is it required? For example, preloading of some remote resource which you don't need display but need to cache.

Gargo
  • 1,135
  • 1
  • 10
  • 21

2 Answers2

0

"Functional Reactive Programming (FRP)" libraries (of which Combine is one example) accept a series of values that arrive over time, transform those values through functions, and deliver the final transformed result to a subscriber.

Ideally the transformation functions should be pure - have no side-effects.

The fundamental purpose of the library is to deliver the transformed values to a subscriber. As a result there is no built-in empty subscriber. That is the answer to your question and the reason behind it.

To put it another way, if the pipeline uses pure functions, and nobody cares about the result, the whole pipeline serves no purpose.

You are relying on intermediate functions in your pipeline that do have side-effects (are not "pure functions"). And you don't care about the final, transformed, values. Your usage is at odds with some of the fundamental tenets of FRP.

In my opinion, I think that is a code smell that you might be using the wrong tool for the job or you might not be using the tool correctly. From what you've described, it seems to me that your pipeline wants to deliver values to the cache. So the save-to-the-cache operation should probably happen in the sink.

At the very least, I would be inclined to have sink generate a log message about the success or failure of the operation. Side effects are to be expected in sink.

Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
  • `sink` should cache nothing by itself in this because other "tools" do that, logging - possibly. I also have a situation when I need to execute a sequential publishers and I'm not sure that their order won't change in future so I chain them with `flatMap` and `catch` and use the empty `sink` – Gargo Jun 30 '23 at 06:34
  • You implement your application in a way that makes sense to you. What you are doing seems odd to me, but I don't have to maintain the code :-) – Scott Thompson Jun 30 '23 at 16:38
0

A direct answer to your questions:

But is it more convenient solution like a class which implements Subscriber protocol but does nothing?

No there isn't. In RxSwift, the closures are optional so you could do subscribe() but nothing like that exists in Combine.

Why is it required?

The reason you have to subscribe to a publisher to activate it is because they are lazy by default. A publisher chain is a description of a procedure, not the execution of of said side effect.

It's an implementation of the Command Pattern. When you create a command object, nothing happens. Only when you call execute() on the object does something happen. The same is true of most Publishers. Creating one does nothing but set up the context. Executing the publisher (by calling sink or one of the other subscription methods) causes things to happen.

Interestingly, Future is an exception to this. If you wrap your side effect in a Future, it will execute its side effect immediately upon construction.

Lastly, side effects either produce something or consume something. Your notion of getting something from a server (a producer) in order to store it in a cache (a consumer) is actually two side effects.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72