The .shared() operator of AsyncAlgorithms package is helpful and I want to use it often, if not always. However, it comes with a tradeoff. The AsyncShareSequence is internal to the package which forces us to use any type, like in the following example:
actor HealthMulticast {
// Type-erased shared sequence. Note that the client who would use HealthMulticast,
// can't infer that it's a shared sequence from it's type,
// and we have to state it in the variable name.
// We also could create a typealias for this purpose.
let heartRateShared: any AsyncSequence<Double, Never> & Sendable
init() {
// ...
heartRateShared = stream.share(bufferingPolicy: .bufferingLatest(1))
}
}
We want to subscribe to the shared sequence, but, let's say, we also wish to throttle it (the following also true for .debounce() or any other modifier).
I wish the following code worked, but it would not compile:
// ERROR: Member '_throttle' cannot be used on value of type 'any Sendable & AsyncSequence<Double, Never>';
// consider using a generic constraint instead
let throttledHeartRate = multicast.heartRateShared._throttle(for: .seconds(10), latest: true)
for await hr in throttledHeartRate {
print(hr)
}
It turns out that we can bypass this limitation by using a generic function that would "unbox" the existential type:
/// This would work on `any AsyncSequence`, unlike `._throttle()` member function from AsyncAlgorithms.
func throttle<S: AsyncSequence & Sendable>(
_ sequence: S,
duration: Duration,
latest: Bool = true
) -> any AsyncSequence<S.Element, S.Failure> where S.Element: Sendable {
sequence._throttle(for: duration, latest: latest)
}
Finally we can consume the shared sequence, however it feels a little bit unnatural for Swift:
// # It looks Pythonish
let throttledHeartRate = throttle(multicast.heartRateShared, duration: .seconds(10), latest: true)
for await hr in throttledHeartRate {
print(hr)
}
It makes me question the language rules. Why a function can unbox 'any' type by being generic and a modifier _throttle(), which is defined on AsyncSequence can't do the same? What is "unboxing" even? – a dynamic access to a type hidden under a protocol? – Why can't we unbox types wherever we want?