How to make combine's prefix operator inclusive?

You're right. Do it with flatMap instead.

import Combine

enum Cut<Value> {
    case output(Value)
    case end(Bool)

    var isEnd: Bool {
        if case .end(_) = self { return true }
        return false
    }

    var output: Value? {
        if case .output(let output) = self { return output }
        return nil
    }
}

extension Publisher {
    func cut(after predicate: @escaping (Output) -> Bool) -> AnyPublisher<Output, Failure> {
        return self.flatMap { (input: Output) -> AnyPublisher<Cut<Output>, Failure> in
            if predicate(input) {
                return [.output(input), .end(true)].publisher.setFailureType(to: Failure.self).eraseToAnyPublisher()
            } else {
                return [.output(input)].publisher.setFailureType(to: Failure.self).eraseToAnyPublisher()
            }
        }
        .prefix { !$0.isEnd }
        .compactMap { $0.output }
        .eraseToAnyPublisher()
    }
}

let subject = PassthroughSubject<String, Never>()

let ticket = subject.cut(after: { $0.hasPrefix("end-") })
    .sink(
        receiveCompletion: { print("completion: \($0)") },
        receiveValue: { print($0) }
    )

subject.send("hello")
subject.send("world")
subject.send("end-transmission")

But there's a bug that makes it crash if the Cut.end case has no associated value. Hence the dummy Bool value.

1 Like