4

Experimenting with Swift's concurrency, I would like to have a clean API for exposing an async sequence of a given element type and a throttled version of the same:

  var intStream: AsyncStream<Int> {
    AsyncStream<Int>(Int.self, bufferingPolicy: .bufferingNewest(5)) { continuation in
      Task.detached {
        for _ in 0..<100 {
          try await Task.sleep(nanoseconds: 1 * 1_000_000_000)
          continuation.yield(Int.random(in: 1...10))
        }
        continuation.finish()
      }
    }
  }
  
  var throttledIntStream: AsyncStream<Int> {
    intStream.throttle(for: .seconds(2))
  }

But this does not work as throttle returns its own type: Error: Cannot convert return expression of type 'AsyncThrottleSequence<AsyncStream<Int>, ContinuousClock, Int>' to return type 'AsyncStream<Int>'

To get type erasure I could do

var throttledIntStream: some AsyncSequence {
  intStream.debounce(for: Duration.seconds(2))
}

but then I lose the element type information as well, which I would like to keep.

Any suggestions how to best solve that?

Edit: This is pointing to the solution I want, but I guess I will need to wait https://forums.swift.org/t/anyasyncsequence/50828/2

Nicolas Degen
  • 1,522
  • 2
  • 15
  • 24
  • 1
    I think you'd need to invent your own `AnyAsyncSequence` extension. Seeing how [`AnySequence` is implemented](https://github.com/apple/swift/blob/main/stdlib/public/core/ExistentialCollection.swift#L1246), it's rather convoluted (I think you'd also need `AnyAsyncIterator`), but I think it is still *doable*. – Sweeper Apr 05 '22 at 10:02
  • I do not need to invent it I think, I would just to have a `.eraseToAnySequence()` function... But yes, `AnySequence` would be the solution – Nicolas Degen Apr 05 '22 at 12:13

2 Answers2

1

You could define an extension on AsyncSequence that wraps the underlying sequence in an AsyncStream/AsyncThrowingStream:

extension AsyncSequence {
    func toAsyncThrowingStream() -> AsyncThrowingStream<Element, Error> {
        var asyncIterator = self.makeAsyncIterator()
        
        return AsyncThrowingStream<Element, Error> {
            try await asyncIterator.next()
        }
    }
    
    func toAsyncStream() -> AsyncStream<Element> {
        var asyncIterator = self.makeAsyncIterator()
        return AsyncStream<Element> {
            try! await asyncIterator.next()
        }
    }
}

Which you can use like this:

var intStream: AsyncStream<Int> {
    AsyncStream<Int>(Int.self, bufferingPolicy: .bufferingNewest(5)) { continuation in
      Task.detached {
        for _ in 0..<100 {
          try await Task.sleep(nanoseconds: 1 * 1_000_000_000)
          continuation.yield(Int.random(in: 1...10))
        }
        continuation.finish()
      }
    }
  }
  
  var throttledIntStream: AsyncStream<Int> {
    intStream.throttle(for: .seconds(2)).toAsyncStream()
  }

The big drawback of this approach is that to my knowledge there is no way of knowing if the underlying async sequence is a throwing or non-throwing one, thus one cannot implement a solution generic on the failure type.

This means that it is entirely the programmer's responsibility to chose the suitable adaptor toAsyncStream or toAsyncThrowingStream.

As you mentioned in your question, your problem will probably be solved when better opaque return types will land.

Louis Lac
  • 5,298
  • 1
  • 21
  • 36
0

What problem to use

var throttledIntStream: AsyncThrottleSequence<AsyncStream<Int>, ContinuousClock, Int>{
  intStream.throttle(for: .seconds(2))
}

Where did you get AsyncThrottleSequence? Anyway you can extend AsyncStream with something like:

extension AsyncStream {
  typealias throttled = AsyncThrottleSequence<AsyncStream<Element>, ContinuousClock, Element>
}

And use it as:

var throttledIntStream: AsyncStream<Int>.throttled {
    intStream.throttle(for: .seconds(2))
  }
Cy-4AH
  • 4,370
  • 2
  • 15
  • 22
  • 1
    `.throttle` is from https://github.com/apple/swift-async-algorithms I agree that the typalias is a nice workaround, but not the solution I am looking for! – Nicolas Degen Apr 05 '22 at 12:08
  • @NicolasDegen then use `AsyncThrottleSequence, ContinuousClock, Element>` it's in the apple's paradigm, you can see it after applying several operators if try specify variable type manually. – Cy-4AH Apr 05 '22 at 14:10