Swift Async Algorithms 1.1 Planning

Swift Async Algorithms started off with a goal for source stability. This means that building against newer versions should not break older code using it.

Since we signed up for semantic versioning no public interface shall be broken (and the only potential exception to this is a transition of major versions). However we do want to ensure that versioning is meaningful and follows a cadence of regular releases. Tying releases to a specific chronological guarantee is not nearly as meaningful since it is not tied to a binary release of an operating system nor is it tied to any specific cadence of releases of hardware. Instead it makes more sense to build semantic release cadences around feature sets that indicate the start and end of periods of work around certain ideas.

The 1.0 release was built around getting a set of ideas of the fundamental algorithms to build a framework that is cohesive. This meant expanding upon the AsyncSequence types provided in the standard library (specifically the _Concurrency module) for the ones that would still be commonly needed but not the most fundamental expected for any use. Part of the focus was that of interfacing with clocks and durations, others were focused upon buffering and splitting.

Since we have a released semantic version we need to demarcate the start of a new release for development. Swift modules thankfully have the capability of being specified of a certain branch or release and can specify a valid range for use. This means that developers may be able to specify the range of usage as 1.0 or allow 1.0 and beyond or require 1.1 or later. It is expected that any cut release version should be self contained and usable within that version. That means our qualification of which should ensure that any start of development for 1.1 should contain stable development for landing that released version. Since any new development will be on the main branch, any minor fixes that need to be applied to the 1.0.4 release would then be cherry picked (if needed) and the version of the release would then be bumped to 1.0.5. It is also expected that those fixes would be present on the main branch such that they are available for the eventual 1.1.

So at this point the question is: what constitutes 1.1? Since the idea is not to be bound to a yearly cadence (either shorter or longer than year) but instead bound to a conceptual grouping - 1.1 will have a conceptual grouping of adding in functionality to replace existing non-Swift-Concurrency systems with Swift Concurrency.

So far there are a few important algorithms to bring forward for this release. These all have similar systems that exist in other frameworks or are intrinsically linked to interfacing with non-Swift-Concurrency systems.

  • share()
  • MultiProducerSingleConsumerChannel
  • enumerated()
  • deferred()
  • withDeadline
  • withLatestFrom
  • mapFailure
  • throttle (this is just a revisit of behaviors)

In addition to these areas of focus there are a few language improvements that are worth considering as well.

  • variadic generics (N-ary zip et al)
  • adopting some types (by deprecating old initializers and adding new ones with typed throws)
  • typed throws

Of course there is a chance where not all of these will make it; for 1.0 throttle was deferred from the release due to complications with the behaviors. However, any deferred algorithm from 1.1 will be something of strong consideration for 1.2.

AsyncAlgorithms is not my sole responsibility so I will need some help from the community to make this deliverable happen. Some folks have already posted patches for the algorithms and features listed. In order to move them forward the major things that need to happen is first and foremost communication around what is and is not finished (the other requirements are somewhat flexible and time permitting can be relaxed), the other (more flexible requirements) are a write-up of a proposal, an implementation, and tests covering at least a good swath of the behaviors and code coverage. In the coming days I will be either promoting bugs or writing up issues for a 1.1 milestone.

This general process is something that I would also like to refine and perhaps advocate to other packages like swift-algorithms or numerics etc. Some of this process was inspired from some insight from @nnnnnnnn from ArgumentParser.

I would love to hear what aspects of this quasi-pitch should be refined or changed to better suit the needs of the community.

22 Likes

I believe there were some ideas and snippets here that might give an engineer a head start on that:

2 Likes

Is retry something AsyncAlgorithms would be interested in? Certainly not the most complicated algorithm implementation-wise, but I still had a need for it in the past, and Combine for instance has it.

Besides that, is splitting up the algorithms into submodules something that was considered? swift-collections has this, although swift-algorithms does not, so there is no preferred way I guess. But I mostly only use 1-2 algorithms when I depend on AsyncAlgorithms myself.

3 Likes

I believe the solution being considered is package traits:

3 Likes

Unless I'm missing something, there's really no need to overthink this. Semantic versioning with SPM is essentially zero cost, so there's no reason not to release as often as you can. This is especially true of bug fixes, but new features are appreciated by the community as quickly as you can produce them.

Since Xcode doesn't support package traits, this doesn't really help most Swift developers. Sure, you can work around that limitation by using a local package for those dependencies, but that's a non-obvious solution that most won't see and so won't see the features hidden by the traits.

9 Likes

Yep that would definitely fit - anything in that category of "needed it but Combine had it" would be under the banner of "replacements for existing non-Swift-Concurrency".

This one I am hesitant on unless strongly motivated by a compelling reason. AsyncAlgorithms isn't a huge module binary cost wise so that really doesn't justify to me the maintenance and development burden.

This is perhaps the most compelling reasoning why not to focus on that for this cycle.

True, and I don't want to tie things down to a yearly cadence requirement; if we can get things together before that timeframe to a point that seems satisfactory then bumping the version seems like the right move to me. Per small fixes - I think as those come up I am willing to cut minor update releases as things arise. Usually I just need someone to ask and I think cutting those is not a huge deal.

6 Likes

Very happy to see this direction - just to clarify, the throttling semantics is what we previously discussed in:

Or are there something else in addition that I missed?

2 Likes

My apologies for asking this dumb question, but why is MultiProducerSingleConsumerChannel considered an algorithm?

Isn't it just a data structure, which an algorithm might use to solve a problem? For example, a sorting algorithm might use an Array, but can an Array be called an algorithm?

1 Like

I’m interested in a sort of fork-map thing, where:

Backbone async sequence of A elements
β•Œβ•Œβ•Œ ● β•Œβ•Œβ•Œβ•Œ ● β•Œβ•Œβ•Œ ● β•Œβ•Œβ•Œβ•Œ
β†˜οΈŽ (map to async sequence of AΚΉ)
  β•Œβ•Œβ•Œ β–  β•Œβ•Œβ•Œβ•Œ β–  β•Œβ•Œβ•Œ β–  β•Œβ•Œβ•Œβ•Œ β‡’ consumer of AΚΉ
β†˜οΈŽ (map to async sequence of AΚΊ)      
  β•Œβ•Œβ•Œ β—† β•Œβ•Œβ•Œβ•Œ β—† β•Œβ•Œβ•Œ β—† β•Œβ•Œβ•Œβ•Œ β‡’ consumer of AΚΊ

where a base element can be wrapped, recast, and processed by several different consumers.

Is this what share() would help with, or does this need a different solution?

1 Like

That would be something like

let shared = base.share()
let a = shared.map { ... }
let b = shared.map { ... }

Iβ€˜m no longer actively using Swift nowadays, however I wouldn’t mind if APIs were introduced to at least allow opting out of the eager cancellation behavior of the stream family types.

This is long overdue imo.

3 Likes

Is a migration from XCTest to Swift Testing also something that is being / should be tackled? I know that this is not relevant for any release, but I guess for writing tests for new algorithms.

I checked AsyncAlgorithms_XCTest and it seemed like it can easily be converted (and AsyncSequenceValidation is test system agnostic), the rest also does not seem like undoable task in terms of complexity.

Edit: I guess the performance tests are not yet possible.

Migrating is a reasonable task at any point in time and might provide useful feedback for the Swift Testing proposals / pitches that folks might be interested in pushing.

1 Like

My intial thought was, that not every algorithm requires swift-collections, but I guess the binary size impact of that is also minimal.

Something Iβ€˜ve also thought about recently is, if we could introduce an Evolution.md (similar to what swift-foundation has) to swift-async-algorithms. I couldnβ€˜t say Iβ€˜m 100% certain on how evolution works in this project.